Javaのbyteとは?byteと基本データ型・文字列の変換を詳細に!
Javaのbyteはプリミティブ型の一つで、8ビットの範囲で-128~127までの整数を表現するものです。
byteは主にバイナリファイルやバイナリデータを扱う時に使います。人によってはbyteは使ったことがないかもしれませんが、Java 9以降のStringは内部的にbyteで文字列を管理していますし、実はbyteは結構身近にあるのです。
ですから、byteの扱い方や考え方を知っておくと、いざプログラムでバイナリデータを扱わなければならなくなった時に慌てずに済みます。そして、バイナリデータの扱いが分かるということは、コンピュータの動き方の理解に一歩近づくことでもあります。
この記事ではbyteについて、byteとはどういうものか、どうやって使うのか、気を付けたい所などを初心者向けにお伝えします。2進数にまつわる話がどうしても多いですが、そこも分かりやすくお伝えします。
※この記事のサンプルは、Java 11の環境で動作確認しています
1.byteは1バイトを表すデータ型
byteは整数のデータ型で、プリミティブ型(primitive、基本データ型)と呼ばれるものの一つです。一つのbyteのサイズは8ビット(1バイト)で、Javaではもっともビットのサイズが小さいプリミティブ型です。
byteを他のプリミティブ型と簡単に比較すると、以下のとおりです。
データ型 | 値の種類 | ビット数 | 表現できる値の範囲 | 接尾語 | 備考 |
boolean | 真偽値 | 1ビット | true/falseのどちらか | – | |
byte | 整数 | 8ビット(1バイト) | -128~127 | – | |
short | 整数 | 16ビット(2バイト) | -32,768~32,767 | – | |
char | 文字 | 16ビット(2バイト) | 0~65,535 | – | Unicode文字、\u0000~\uffff |
int | 整数 | 32ビット(4バイト) | -2,147,483,648~ 2,147,483,647 | – | 約±21億4千7百万、Unicodeコードポイント |
long | 整数 | 64ビット(8バイト) | -9,223,372,036,854,775,808~ 9,223,372,036,854,775,807 | Lまたはl | 約±922京 |
float | 浮動小数点 | 32ビット(4バイト) | ±3.40282347E+38~ 1.40239846E-45 | Fまたはf | 単精度 約±3.4×10の38乗~約±1.4×10の-45乗 |
double | 浮動小数点 | 64ビット(8バイト) | ±1.79769313486231570E+308~±4.94065645841246544E-324 | Dまたはd | 倍精度 |
1-1.Javaのbyteは-128~127の整数
Javaのbyteでは、-128~127の整数を表現できます。整数ですので、数値は1ずつ変化し、小数はありません。
そして、byteは8ビットなので2の8乗とおり、すなわち256とおりを表現できます。その半分をマイナスの整数として使っています。
byteの8ビットのビットパターンと、実際の数値は以下のように対応します。つまり、byteを+1し続けると-128~127の範囲をぐるぐる回るのです。
5~8ビット目 | 1~4ビット目 | 10進数表現 | 備考 |
0000 | 0000 | 0 | すべてのビットが0 |
0000 | 0001 | 1 | |
0000 | 0010 | 2 | |
… | |||
0111 | 1110 | 126 | |
0111 | 1111 | 127 | Byte.MAX_VALUE |
1000 | 0000 | -128 | Byte.MIN_VALUE |
1000 | 0001 | -127 | |
… | |||
1111 | 1110 | -2 | |
1111 | 1111 | -1 | すべてのビットが1、+1すると0に戻る |
byteは0から始まって1ずつ大きくなり、8ビット目が0かつ1~7ビット目が全て1になると127です。そこから、8ビット目が1かつ0~7ビット目が全て0になると-128で、そこから1ずつ大きくなり、全てのビットが1になると-1です。
1-2.byteの使いどころはデータをバイナリで扱う時
byteはバイナリファイルやバイナリデータを「そのまま」扱う時に使います。もちろん、byteは数値型なのでbyteを使った計算もできますが、プログラム上からの扱いはデータの格納容器であることが多いでしょう。
ですから、byteの変数を1つだけ使うよりも、byteを配列にして、バイナリデータの集まりを表すのによく使われます。ファイルや文字列も、裏ではbyteの集まりです。このように、プログラムで扱えるものは全てbyteで表せるのです。
byte[] java1 = { 0x4a, 0x61, 0x76, 0x61 }; // "Java"のバイナリ表現(UTF-16) String java2 = new String(java1); // Stringのコンストラクタ(String(byte[])) System.out.println(java2); // → "Java" byte[] java3 = "Java".getBytes(); System.out.println(java.util.Arrays.toString(java3)); // → [74, 97, 118, 97] byte[] file = Files.readAllBytes(Paths.get("/A/B/C.dat")); // ファイルC.datのバイナリデータ
byte配列を使ったファイルの読み書きについては、この記事を読み終わった後に、以下の記事も読んでみてください。
関連記事 関連記事2.byteの基本的な使い方
この章では、byteの基本的な使い方について、サンプルを交えながらお伝えします。
2-1.byteの宣言の仕方、初期値、リテラル
2-1-1.byteの変数・配列の宣言、初期値の指定
byteは型の一つですから、以下のように変数や配列変数の型として使えます。もちろんメソッドの引数や、戻り値としても使えます。配列の宣言や値の代入や初期化も、他のプリミティブと同じように行えます。
// byte型の変数の宣言と、初期値の代入 byte b = 100; // byte型の配列の宣言と、各インデックスへの値の代入 byte[] byteArray1 = new byte[3]; byteArray1[0] = 11; byteArray1[1] = 12; byteArray1[2] = 13; // byte型の配列の宣言と、初期化を同時に行う byte[] byteArray2 = { -128, 0, 127 };
2-1-2.byteのリテラルの書き方
byteをリテラルで表現する時は、接頭語をつけなければ10進数です。Java 11の時点では10進数の他に以下の書き方ができます。さらに、“_”で桁区切りが出来るようにもなっています。
- 2進数(“0b”で始まる、Java 7から)
- 8進数(“0”で始まる)
- 10進数(接頭語なし)
- 16進数(“0x”で始まる)
byte b1 = 0; byte b2 = 127; byte b3 = -128; byte b4 = 0b1111; // 2進数表示、10進数で15 byte b5 = 0b0111_1111; // 2進数表示、10進数で127 byte b6 = 017; // 8進数表示、10進数で15 byte b7 = 0xf; // 16進数表示、10進数で15
なお、1バイトの範囲は符号なしの0~255と覚えているのが普通でしょう。ですから、Javaのbyteの変数へ、例えば255、0xff、0b1111_1111が代入できると思いがちです。でも、それはJavaではコンパイルエラーになります。
byte b1 = 255; // コンパイルエラー!! 255は-128~127の範囲内に無いため byte b2 = 0xff; // コンパイルエラー!! 0xffは10進数で255、エラーの理由は↑と同じ byte b3 = 0b1111_1111; // コンパイルエラー!! 0b1111_1111は10進数で255、エラーの理由は↑と同じ
なぜなら、255や0xffはJavaのbyteで表現できる-128~127の範囲に無い数値だからです。ただ、明示的なbyteへのキャストをすれば代入ができます。この仕組みにはintとbyteの変換の仕様が関係しますが、詳しくは後述します。
byte b1 = (byte) 255; // byteにキャストすると、byteでの-1(1111 1111)になる byte b2 = (byte) 0xff; // 同上 byte b3 = (byte) 0b1111_1111; // 同上
ただ、0~255の範囲で数値を扱って、それをbyteとの間で自由に変換できれば、それはそれで分かりやすいこともありますし、便利です。そのための方法もありますので、こちらも後述します。
2-1-3.byteのフィールド、配列の初期値は0
フィールドとしてbyte型の変数を使ったり、byte型の配列を使う場合は、未初期化だと0になります。他の数値型プリミティブと同じ動きです。
class ByteTest { byte b; // byteをフィールドとして宣言したが、初期値は未設定 public static void main(String[] args) { ByteTest byteTest = new ByteTest(); System.out.println(byteTest.b); // → 0、宣言時に未初期化なのでデフォルト値が設定される byte[] array = new byte[3]; System.out.println(array[0]); // → 0、newした時点で配列全体が0で初期化される } }
2-2.byteを使った四則演算
byteを使った四則演算(加減乗除)には、四則演算用の算術演算子、つまり+-*/を使います。余りを求めるなら%です。
ただし、計算結果で小数点が出た場合、byteの変数へ代入すると、小数点以下の値は切り捨てされます。これは整数型のプリミティブに共通する動きです。
byte i = 10; byte j = 3; byte plus = (byte)(i + j); byte minus = (byte)(i - j); byte multiply = (byte)(i * j); byte divide = (byte)(i / j); byte surplus = (byte)(i % j); System.out.println(plus); // → 13 System.out.println(minus); // → 7 System.out.println(multiply); // → 30 System.out.println(divide); // → 3(計算上は10 / 3 = 3.333…だが、小数点以下が切り捨てられて0になる) System.out.println(surplus); // → 1
なお、Javaではbyte/shortへの演算はintに自動的に変換して行うのが仕様で、計算結果もintになります。ですので、計算結果をbyteにするなら、例のようにbyteへの明示的なキャストが必要です。
そして、byte以外のプリミティブ型からbyteにキャストする際は注意が必要です。どういう扱いがされるかは後述します。
2-2-1.byteの計算では、符号の変化に要注意!!
byteを扱う時は、符号の変化に注意しましょう。
byteに数値を加減算して、プラス側の最大値を超えるとマイナス側の最大値になり、マイナス側の最小値よりも小さくなるとプラス側の最大値になります。つまり、加算していくとプラスマイナスがひっくり返るタイミングがあり、オーバーフローと呼ばれます。
byte b1 = Byte.MAX_VALUE; byte b2 = (byte)(b1 + 1); byte b3 = (byte)(b1 + 2); System.out.println(b1); // → 127 System.out.println(b2); // → -128 System.out.println(b3); // → -127 byte b4 = Byte.MIN_VALUE; byte b5 = (byte)(b4 - 1); byte b6 = (byte)(b4 - 2); System.out.println(b4); // → -128 System.out.println(b5); // → 127 System.out.println(b6); // → 126
これは、二進数で言うと 0111 1111 と 1000 0000 の間でプラスマイナスが変わるということです。前者がByte.MAX_VALUE、後者がByte.MIN_VALUEです。これを細かく言えば、Javaでは2の補数でマイナスの数値を表現しているから、ということです。
byteは表現できる範囲が8ビットと小さいので、特に加算や乗算をするとすぐに範囲を超えてしまいます。ですから、計算結果を格納する変数は、byteよりもビット数が大きな型のshort、intなどにしておいてもいいでしょう。
byte b1 = 100; byte b2 = 125; byte sum = (byte)(b1 + b2); System.out.println(sum); // → -31
2-3.byteでのビット演算
byteはビット演算にも使えます。業務向けのプログラムではビット演算はあまり使いませんが、科学技術計算など処理速度が必要なプログラムでは、ビット演算で計算することもあります。特に、掛け算や割り算をシフトで代用するのが代表的です。
なお、C言語などの名残があるプログラムだと、処理のオプションをビットパターンで指定したりします。また、プログラムの仕様で特定のビットが1か調べることもありますので、ビット演算のやり方は覚えておいても損はありません。
以下では、Javaでbyteが対象のビット演算の例を簡単に示しておきます。ただ、Javaのビット演算はshortとbyteではintに変換後に実行され結果もintなので、特に右シフトで論理シフト(>>>)をするなら事前に0xffと&しておく必要があるなど、工夫が必要です。
public class BitOperatorSample { public static void main(String[] args) { byte b1 = 0b101; byte b2 = 0b010; // 論理和(or) | byte or = (byte) (b1 | b2); System.out.println(toBinaryString(or)); // → 00000111 // 論理積(and) & byte and = (byte) (b1 & b2); System.out.println(toBinaryString(and)); // → 00000000 b1 = 0b1100; b2 = 0b1010; // 排他的論理和(xor) ^ byte xor = (byte) (b1 ^ b2); System.out.println(toBinaryString(xor)); // → 00000110 b1 = 0b1010; // 論理否定(not) ~ byte not = (byte) ~b1; System.out.println(toBinaryString(not)); // → 11110101 b1 = 0b111; // 左シフト << byte ls = (byte) (b1 << 1); System.out.println(toBinaryString(ls)); // → 00001110 b1 = -2; // 1111 1110 // 右シフト(算術シフト) >> byte rs1 = (byte) (b1 >> 1); System.out.println(toBinaryString(rs1)); // → 11111111 // 右シフト(符号なし、論理シフト) >>> byte rs2 = (byte) ((b1 & 0xff) >>> 1); // 0xffと&して、intでの9ビット目を0にしてからシフト System.out.println(toBinaryString(rs2)); // → 01111111 } private static String toBinaryString(byte b) { return String.format("%8s", Integer.toBinaryString(b & 0xff)).replace(" ", "0"); } }
3.byteと他のプリミティブ型との変換
Javaでは数値型のプリミティブ型として、byteとビット数が違う整数(short/char/int/long)と、小数点が扱えるもの(float/double)があります。
byteをビット数がより大きいプリミティブへ変換する際は数値が維持されるのですが、逆に、ビット数が大きなプリミティブからbyteへ変換する際には、知っておくべきことがいくつかあります。この章ではそれをお伝えします。
ちなみに、前者はプリミティブ型のワイドニング変換(Widening Primitive Conversion)、後者はナローイング変換(Narrowing Primitive Conversion)とも呼ばれます。
3-1.short/char/int/long→byteへのキャスト
このパターンは、ビット数が大きな方の整数から、小さい整数(この記事ではbyte)のビット数分のデータだけを、ビットパターンをそのままに取り出すイメージです。言葉だと分かりづらいので、具体的な例で説明してみます。
short s0 = 254; // 0000 0000 1111 1110 System.out.println(s0); // → 254 byte b0 = (byte) s0; System.out.println(b0); // → -2、1111 1110 short s1 = 255; // 0000 0000 1111 1111 System.out.println(s1); // → 255 byte b1 = (byte) s1; System.out.println(b1); // → -1、1111 1111 short s2 = 256; // 0000 0001 0000 0000 System.out.println(s2); // → 256 byte b2 = (byte) s2; System.out.println(b2); // → 0、0000 0000
16ビットであるshortの254は、ビットパターンでは 0000 0000 1111 1110 です。これを8ビットのbyteにキャストすると、後ろの8ビット分だけが取り出されます。ですので、byteのビットパターンは 1111 1110 となって、これを先述した表で見てみると-2ですよね。
同じように、shortで255のビットパターンは 0000 0000 1111 1111 で、byteにキャストすると 1111 1111、すなわち-1です。さらにshortで256は 0000 0001 0000 0000 で、byteにキャストすると 0000 0000、すなわち0です。
3-2.byte→short/char/int/longへのキャスト
このパターンでは、ビット数が大きな方の整数に、小さい整数(この記事ではbyte)で表現されている数値がそのまま入ります。こちらも具体的な例で説明してみます。
byte b0 = -2; System.out.println(b0); // → -2 short s0 = (short) b0; System.out.println(s0); // → -2 byte b1 = -1; System.out.println(b1); // → -1 short s1 = (short) b1; System.out.println(s1); // → -1 byte b2 = 0; System.out.println(b2); // → 0 short s2 = (short) b2; System.out.println(s2); // → 0
8ビットであるbyteの-2は、ビットパターンでは 1111 1110 です。これを16ビットのshortにキャストしても、-2という数値はそのままです。shortの-2のビットパターンは 1111 1111 1111 1110 です。つまり、同じ数値なのですが、型が違うのでビットパターンも違うのです。
同じように、byteの-1は 1111 1111、shortにキャストすると数値は-1のままで 1111 1111 1111 1110 です。さらにbyteの0は 0000 0000 で、shortにキャストすると数値は0、すなわち 0000 0000 0000 0000 です。
3-3.float/double→byteへのキャスト
このパターンでは、float/doubleで表現されている数値が一旦32ビット整数のintに変換され、その後にビットの大きい整数→小さい整数のパターンで変換されます。float/doubleの浮動小数点型とbyte/intなどの整数型ではビットパターンに互換性がありませんので、一旦intを経由するのですね。
float f0 = 254.5f; System.out.println(f0); // → 254.5 byte b0 = (byte) f0; System.out.println(b0); // → -2 float f1 = 255.5f; System.out.println(f1); // → 255.5 byte b1 = (byte) f1; System.out.println(b1); // → -1 float f2 = 256.5f; System.out.println(f2); // → 256.5 byte b2 = (byte) f2; System.out.println(b2); // → 0
floatが254.5の時の場合だけ解説します。まずは254.5が32ビットのintへ変換されます。この際、小数点以下は切り捨てられ、254になります。32ビットのintで254のビットパターンは 0000 0000 0000 0000 0000 0000 1111 1110 です。この後ろ8ビット分がbyteになるので 1111 1110 となり、数値としては-2です。
なお、float/doubleからのキャスト先がlongの場合だけ、途中に出てくる整数型はlongになります。それ以外の整数型の場合はintです。この変換の詳細は、整数→整数の場合も含め、Java言語仕様の「Narrowing Primitive Conversion」に記述されています。
3-4.byte→float/doubleへのキャスト
このパターンでは、byteで表現されている値がfloat/doubleにそのまま入ります。そもそもfloat/doubleはbyteの範囲の数字を全部表現できますので、数値そのものでやりとりできるのです。
byte b0 = -2; System.out.println(b0); // → -2 float f0 = (float) b0; System.out.println(f0); // → -2.0 byte b1 = -1; System.out.println(b1); // → -1 float f1 = (float) b1; System.out.println(f1); // → -1.0 byte b2 = 0; System.out.println(b2); // → 0 float f2 = (float) b2; System.out.println(f2); // → 0.0
3-5.【応用】byteを0~255で扱いたい場合は、Integer.toUnsignedIntかビット演算を使おう
byteで扱える-128~127の範囲の数値を、0~255の範囲で扱えるとプログラムが分かりやすくなることがあります。その際にはbyteをintなどに変換するのですが、この章でお伝えしたとおり、byteからintへの単純なキャストでは意図したとおりに変換できません。
そこで、Java 8以降ではByte.toUnsignedIntを使うと、いい感じに変換ができるのでお勧めです。
byte b = -1; // byteの-1はビットパターン 1111 1111、符号なしなら255 int i1 = b; // こちらではintにそのままキャスト int i2 = Byte.toUnsignedInt(b); // こちらではByte.toUnsignedIntで変換 System.out.println(i1); // → -1 System.out.println(i2); // → 255!!
Java 7以前の環境なら、0xffとビット演算の&(論理積、AND)をした結果でいいでしょう。こうすると、byteで表現している数値ではなく、byteの8ビット分のビットパターンをそのままintにした数値を得られます。ビット演算は、数値ではなくビットパターンで処理するからですね。
byte b = -1; // byteの-1はビットパターン 1111 1111、符号なしなら255 int i = b & 0xff; // ここでbyteと0xffをandすると… System.out.println(i); // → 255!!
ちなみに、Byte.toUnsignedIntでやっているのも同じことです。ただ、ビット演算を使えば、変換先はshortでもOKです。
そして、0~255までの範囲のintを、-128~127のbyteに再度変換するには、単にbyteへキャストすればOKです。この仕組みもこの章でお伝えしましたよね。
int i = 255; // intの255は0000 ... 1111 1111、後ろの8ビットをそのままbyteの-1にしたい… byte b = (byte)i; // そんな場合はintをbyteにキャストすれば、 System.out.println(b); // → -1!!
4.byteとStringとの変換
StringもJavaでは重要なクラスです。Stringをbyteにすること、またbyteをStringにできればいろいろと便利です。ここではその方法をお伝えします。
4-1.String→byteの変換
ファイルから読み込んだ文字列や、引数で受け取った文字列からbyteを作りたい時があります。そういう時はByte.parseByteを使いましょう。文字列をbyteに変換してくれます。byteに解釈できない文字列へはNumberFormatExceptionがthrowされますのでご注意を。
String byteStr = "123"; byte b = Byte.parseByte(byteStr); System.out.println(b); // → 123
基数(何進数かを表す数値)を指定するならByte.parseByte(String, int)を使います。二番目の引数には基数を指定します。例えば、2進数なら2ですし、16進数なら16です。
String byteStr = "10"; byte b1 = Byte.parseByte(byteStr, 2); // 2進数として解釈 byte b2 = Byte.parseByte(byteStr, 10); // 10進数として解釈 byte b3 = Byte.parseByte(byteStr, 16); // 16進数として解釈 System.out.println(b1); // → "10"は2進数では2 System.out.println(b2); // → "10"は10進数では10 System.out.println(b3); // → "10"は16進数では16
4-2.byte→Stringの変換
逆に、byteをStringにしたい時があります。簡単にやるなら、Byte.toString(byte)か、String.valueOf(byte)を使いましょう。byteが持つ数値が10進数のStringになります。ちなみに、どちらのメソッドでも結果は同じです。
byte b = 123; String byteStr = String.valueOf(b); System.out.println(byteStr); // → "123"
また、byteは2進数や16進数表記にしたいこともあります。16進数にするならString.formatがお手軽ですが、2進数にするにはもう少し工夫が必要です。以下の例で0xffと&しているところは、Byte.toUnsignedIntでも同じです。
byte b = 123; String byteStr1 = String.format("%02x", b); String byteStr2 = String.format("%8s", Integer.toBinaryString(b & 0xff)).replace(" ", "0"); System.out.println(byteStr1); // → 7b System.out.println(byteStr2); // → 01111011
4-3.byte配列→Stringの変換
実務でよくやるのは、デバッグ用にbyteの配列をStringにしてログに出力することです。
4-3-1.java.util.Arraysを使う
byte配列のString変換を簡単に行うなら、java.util.Arrays.toString(byte[])を使います。byteの多次元配列なら、java.util.Arrays.deepToString(byte[])で良いでしょう。
byte[] b1 = {74, 97, 118, 97}; // "Java" String str1 = java.util.Arrays.toString(b1); System.out.println(str1); // → [74, 97, 118, 97] byte[][] b2 = {{74, 97, 118, 97}, {69, 78, 71, 73, 78, 69, 69, 82, 46, 67, 76, 85, 66}}; // "Java"、"ENGINEER.CLUB" String str2 = java.util.Arrays.deepToString(b2); System.out.println(str2); // → [[74, 97, 118, 97], [69, 78, 71, 73, 78, 69, 69, 82, 46, 67, 76, 85, 66]]
4-3-2.自分で変換用のクラス・メソッドを作る
ただ、Arrays.toStringやdeepToStringは10進数で表示されます。2進数や16進数の場合が分かりやすい場合も多いでしょう。その場合は以下の様なクラス・メソッドを作ります。書式はお好みで変えてください。
import java.util.ArrayList; import java.util.List; import java.util.function.Function; class ByteArrayConverter { static String toString(byte[] bytes, Function<Byte, String> converter) { List<String> l = new ArrayList<>(bytes.length); for (byte b : bytes) { l.add(converter.apply(b)); } return String.join(" ", l); } static String toDecimalString(byte b) { return String.format("%d", b); } static String toHexString(byte b) { return String.format("%02x", b); } static String toBinaryString(byte b) { return String.format("%8s", Integer.toBinaryString(b & 0xff)).replace(" ", "0"); } public static void main(String[] args) { byte[] bytes = {74, 97, 118, 97}; String str1 = ByteArrayConverter.toString(bytes, ByteArrayConverter::toDecimalString); String str2 = ByteArrayConverter.toString(bytes, ByteArrayConverter::toHexString); String str3 = ByteArrayConverter.toString(bytes, ByteArrayConverter::toBinaryString); System.out.println(str1); // → 74 97 118 97 System.out.println(str2); // → 4a 61 76 61 System.out.println(str3); // → 01001010 01100001 01110110 01100001 } }
なお、今ではApache Commonsなどの外部ライブラリにも変換用のクラスがありますし、Java 10までならJava SEの環境でDatatypeConverterというクラスが使えます。
ですが、外部ライブラリはプロジェクトで自由に使えないこともありますし、DatatypeConverterはJava 11以降のSE環境では標準で使えなくなりました。ですので、どのように変換するのかを知っておくのは悪いことではありません。
5.byteとByte
Javaでは8ビットの整数を表現するために、byteとByteの二つの方法があります。Javaプログラミングの初心者は、なぜ同じ整数の表し方が二つあるのか混乱すると思います。
この章ではその理由と、byteとByteの使い分けの方針などをお伝えします。
5-1.二種類の表現方法は性能確保のため
Javaではプリミティブ型のbyteと、クラスのByteは別物です。C#などではこういう区別がないのに、なぜJavaではあるのか。これは、Javaが生まれた当時にプログラムの実行速度を確保するためでした。
Javaは1995年に登場したプログラミング言語です。当時のCPUのクロック周波数は今とは桁が違い、一般向けのCPUでようやく100MHzを超えたくらい。メモリの量も全体で数MB~数10MBと非常に乏しかったものです。
byteは8ビットの数字そのものなので、楽に速く扱えます。しかし、byteをクラスとすると、一つのbyteの数値を表すのに8ビットよりもずっと多くのメモリを使います。簡単な計算をするにも、処理上では余分なオーバーヘッドが発生します。
5-2.クラスのByteならnullを表現できる
JavaでByteを使うのは、Byteが持つメソッドを使いたい時と、値がない場合…すなわちnullを表現したい時です。例えば、SQLでは値の有り無しをNULLかどうかで表現できますが、それをJavaのbyteでは上手に表現できません。
ですから、プログラム上では0や-1などの値に特別な意味を持たせたり、Byte.MAX_VALUEやMIN_VALUEを使うのですが、確実さには欠けます。そういう値のチェックを忘れるなどのミスもしがちです。
そういう時に参照型であるByteを使えば、値がないことをnullとして表現できるのです。Byteをどういう時に使うか分からない方は、その変数でnullを表現する必要があるかを一つの指針にしてみてください。
5-3.オートボクシングでbyteとByteを自動変換する
Java 1.5でオートボクシング(auto boxing)という仕組みが導入されました。オートボクシングで、byteとByteをプログラム上でほぼ同じものとして扱えます。
プログラム上でbyteを使う所ではByteを使えますし、Byteを使う所ではbyteが使えます。本当のプログラム上は相変わらずbyteとByteは別物なのですが、その違いをJavaが裏で自動的に変換をしてくれるのです。
Byte byteObj = Byte.valueOf("127"); // Byte byte bytePri = 127; // byte bytePri = byteObj; // → 127、byteにByteを代入できる bytePri = Byte.valueOf("127"); // → 127、同上 byteObj = 127; // → 127、Byteにリテラルを代入できる
これでJavaの面倒な部分がある程度解消されました。ですが、前述のとおりByteはnullを表せますが、byteは必ず何かの整数なので、nullに相当するものがありません。
ですので、以下のように予期せぬところでNullPobyteerExceptionが発生したりします。これは2019年のJava 11の時点でも変わっていません。プログラマが注意するか、Optionalを使う必要があります。
class ByteTest { static Byte returnByte() { return null; } public static void main(String[] args) { byte b = returnByte(); // → nullをbyteに変換できないので、実行するとNullPobyteerExceptionが発生する!! } }
5-4.【応用】符号なしbyteとしての扱い方
この記事ではずっと、byteはプラスマイナスがある8ビットの整数だとお伝えしてきました。それは今も変わりませんが、Java 8でbyteを符号なし整数として扱えるメソッドがByteに追加されました。それらのメソッドを使うと、byteの8ビットをフルに使った計算ができます。
例えば以下のようにです。比較しているのは同じbyteでビットパターンも変わりませんが、compareUnsignedでは8ビット目も含めた整数として比較しているのが分かるでしょうか。
byte b1 = Byte.MAX_VALUE; // → 0111 1111 byte b2 = Byte.MIN_VALUE; // → 1000 0000 int cmp1 = Byte.compare(b1, b2); int cmp2 = Byte.compareUnsigned(b1, b2); System.out.println(cmp1); // → 255(b1の方が大きい)、b1はプラスなのでb2よりも大きい System.out.println(cmp2); // → -1(b2の方が大きい)、8ビットをフルに使っているため、b2の方が大きいと判断される
Byte.~Unsigned系のメソッドは以下のものです。それぞれの詳細は、Javadocを参照してみてください。
Byte.compareUnsigned:符号なし整数として大小比較する
Byte.toUnsignedInt:符号なし整数としてintに変換する
Byte.toUnsignedLong:符号なし整数としてlongに変換する
6.まとめ
この記事では、Javaのbyteをお伝えしてきました。Javaのbyteは1バイト(8ビット)の整数で、-128~127までの値を表現できます。byteはJavaでバイナリデータをバイト単位で使う時の標準的なデータ型です。
Javaでは、1バイト分の整数を表現するにはプリミティブ型のbyteと、クラス(参照型)としてのByteの二種類があり、それらは違うものであることには注意しましょう。ただ、オートボクシングにより違いが見えにくくはなっています。
さて、byteはプログラミングやコンピュータの基本となるデータ単位です。ですので、Javaでbyteの使い方を学んでおけば、他のプログラミング言語でもそう大きな違いなく使えるでしょう。
特にビットパターンと数値の対応を覚えておけば、いざという時に役に立ちます。トラブル対応などで詳細なデータを見る時は16進数などの形式で見ることが多いので、そういう知識があるなしでは作業効率が大きく違いますよ。
コメント