理系学生日記

おまえはいつまで学生気分なのか

オートボクシングの罠

とある Google Group で話題に上がっており、ぼく自身これに起因するバグを見たことがあるので紹介します。このプログラムの出力は予想できるでしょうか。

public class TestInteger {
	public static void main(String[] args) {
		Integer a127, b127, a128, b128;
		a127 = 127;
		b127 = 127;
		a128 = 128;
		b128 = 128;
		
		System.out.println( "a127 == b127: " + (a127 == b127) );
		System.out.println( "a128 == b128: " + (a128 == b128) );
	}
}

この出力は、下記のようになります。

$ java TestInteger
a127 == b127: true
a128 == b128: false

直感と異なるのは、おそらく "a127 == b127" が true になることだと思います。なぜなら、a127 と b127 は互いに参照型であり、参照型に対する等価演算子はそれぞれが同一のオブジェクトを指しているときにのみ true となります。a127 と b127 は異なるオブジェクトを指すはずなのですから、これは本来 false になるべきだとは思いませんか。
しかしこれは、JLS に記述された仕様です。

If the value p being boxed is true, false, a byte, a char in the range \u0000 to \u007f, or an int or short number between -128 and 127, then let r1 and r2 be the results of any two boxing conversions of p. It is always the case that r1 == r2.

Java SE Specifications

Integer を使用した場合、-128 ~ 127 の範囲の整数は Integer オブジェクトとしてキャッシュされており、同一のインスタンス(を指す参照)がボクシング変換で返却されます。そもそもはパフォーマンスを稼ぐための仕様*1なのですが、個人的にはバグを生むだけのような気がするんですけどね。

上記のようなシンプルなプログラムだとすぐに気付きやすいのですが、int と Integer の変数が混在するようなプログラムでは、よく確認しないと if 文が意図通りに動かないなどのボクシング変換の罠にはまります。ご注意して頂ければ。

*1:上記 JLS のページの Discussion を参照