ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

Java における byte 型について (あるいはバイナリデータを扱うためのキャスト)

この記事は、もともと次のページに書かれていた内容に、加筆・修正を行ったものです。

はじめに

この記事では、ビット列を角括弧で囲み、[00000000] のように表現する事にします。

byte 型の表現範囲

Java のプリミティブ型 (基本データ型) の 1 つに byte 型というものがあります。 その名の通り 1 バイト (8 ビット) のサイズの整数値を表現できます。

つまり、[00000000] から [11111111] まで表現することができます。

数値でいうと 0 (0x00) から 255 (0xFF) まで表現できる、と思うところですが、実際には Javabyte 型は符号付きであり、表現できる範囲は -128 から 127 までとなっています。

The values of the integral types are integers in the following ranges:

  • For byte, from -128 to 127, inclusive
  • (略)
Chapter 4. Types, Values, and Variables

バイナリデータを扱うための byte 型のキャスト

符号なしで 0 から 255 の整数を表現したい

上述のとおり byte 型には符号が付いているのですが、バイナリデータの各バイトを 0 (0x00) から 255 (0xFF) の整数値で扱いたい場面がたまにあります。 当然 byte 型のままでは 128 以上の整数値を扱えませんので、short 型や int 型にキャストする必要があります。 逆に、shortint で表現される 0 から 255 までの整数値を byte にキャストしたい場面もあるでしょう。 (例えば InputStream#read() メソッドは、バイトの情報を 0 から 255 までの整数値として返してきます。)

byte 型からの変換

Java SE 8 からは Byte クラスに便利メソッドが用意されているので、それを使うといいでしょう。 (id:nowokay さんに教えていただきました! ありがとうございます!)

Java SE 7 以下では自分でキャストする必要があります。

キャストする場合 (Java SE 7 以下の場合)

byte から intshort へのキャストは、widening primitive conversion です。 このキャストでは、キャストによって数値の情報が失われることがありません。 (キャスト前とキャスト後で数値として同じ値が表現される。)

バイトを非負整数で表現するという目的においては、単にキャストするだけでは MSB (Most Significant Bit) が 1 の場合 (すなわち、値が負の場合) に、期待する結果にはなりません。

byte valueByte = -1; // ビット列で表すと [11111111]
short valueShort = (short) valueByte; // valueShort の値は -1
                                      // ビット列で表すと [1111111111111111]

short 型にしたときにビット列としては [0000000011111111] となって欲しいところですが、元が負値であるために上位ビットに全て 1 が入ってしまっているわけです。 byte 型をキャストして 0 から 255 の整数値の表現にするには、キャスト後に下位 8 ビット以外を 0 にすれば良いでしょう。

byte valueByte = -1; // ビット列で表すと [11111111]
short valueShort = (short) valueByte; // valueShort の値は -1
                                      // ビット列で表すと [1111111111111111]
// 下位の 8 ビットだけ残し、残りを全て 0 にする。
valueShort &= 0xFF; // 期待通りの 255 という数値になる
                    // ビット列で表すと [0000000011111111]

// キャストと下位 8 ビット以外を削る処理を 1 行で行っても良い。
valueShort = (short) (valueByte & 0xFF);

byte 型へのキャスト

一方で、intshort から byte へのキャストは、narrowing primitive conversion です。 このキャストで整数型を byte に変換する場合、単純に元の数値を表すビット列の下位 8 ビットがキャスト後の byte 値となります。

なので、0 から 255 の整数値で表現される値を byte 型にする場合は、単純にキャストしてやればよいのです。

short valueShort = 0xFF; // 数値としては 255。 ビット列で表すと [0000000011111111]
byte valueByte = (byte) short; // 数値としては -1。 ビット列で表すと [11111111]

byte 値を 16 進数表記の文字列にする

C 言語を使う人にはおなじみの printf ですが、似たものが Java にもあります。 (Java SE 5 より導入された模様。)

これを使用すれば、簡単に byte 値を 16 進数表記の文字列にできます。 標準出力などに出力して目で確認したい時にはなかなか便利です。

byte b = (byte) 0xE8; // 数値としては -24

// String#format メソッドを使用して文字列取得。
String bStr = String.format("%02X", b); // => "E8"

// PrintStream#printf メソッドを使用してそのまま出力する。
System.out.printf("%02X", b);

Java SE 1.4 以前は 「Javaのbyte型を16進数で表現」 みたいにまどろっこしいことをしないとダメだったと思います (もしかしたらもっと楽な方法はあるのかも知れませんが) けど、なかなか便利になったものです。