tobiuo1990の日記

プログラマになるブログ

Integer型インスタンスの一部はキャッシュされる。

職場の人がIntegerインスタンス同士を "==" で比較しているコードを目撃して、指摘しようと思いよくよく見てみると、想像に反した挙動になっていたのでちょっと確認してみました。
確認した対象はJava8です。

Integer型インスタンスの同値判定

まずはいくつかのインスタンス生成方法で実験してみます。

new Integer() == new Integer()
Integer a = new Integer(1);
Integer b = new Integer(1);
System.out.println(a == b);

結果は false になります。
aとbは別のインスタンスなので同値判定はfalseです。(これは想定通り)

Integer.valueOf() == Integer.valueOf()
Integer a = Integer.valueOf(1);
Integer b = Integer.valueOf(1);
System.out.println(a == b);

結果は true になります。
valueOfは一度生成したインスタンスを使いまわすっぽい?(これはそもそも想定したことがなかった)

オートボクシング == オートボクシング
Integer a = 1;
Integer b = 1;
System.out.println(a == b);

結果は true になります。
valueOfと同じかな?

Integer.valueOf() == new Integer()
Integer a = Integer.valueOf(1);
Integer b = new Integer(1);
System.out.println(a == b);

結果は false になります。
newしたらすでに等価なインスタンスが存在しても新たにインスタンスを生成してくれるのはStringと同じみたいです。

Integer.valueOf() == オートボクシング
Integer a = Integer.valueOf(1);
Integer b = 1;
System.out.println(a == b);

結果は true になります。
やはりvalueOfとオートボクシングは使いまわすみたいですね。

ここまでの実験でInteger.valueOf()とオートボクシングは等価なインスタンスが既に存在すればそれを使いまわす、ということが言えそうです。
が、少し調べてみると、そうでもない場合があるようです。

Integer.valueOf() == Integer.valueOf() ②
Integer a = Integer.valueOf(128);
Integer b = Integer.valueOf(128);
System.out.println(a == b);

結果は false になります。
オートボクシングでも同様に false になります。

valueOf()メソッドは一部のインスタンスをキャッシュする

Java APIのドキュメントを確認してみます。

このメソッドは、-128から127の範囲(両端含む)の値を常にキャッシュしますが、この範囲に含まれないその他の値をキャッシュすることもあります。

キャッシュするのは-128~127の範囲に限定されているようです。
そのためInteger.valueOf(1)で代入したインスタンス同士は同値となり、Integer.valueOf(128)同士では同値とはならなかったということです。
JDKに付属のソースコードも確認してみます。

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

確かにキャッシュから返したり、newしたインスタンスを返したりしています。

おまけ IntegerCache

IntegerCacheは、Integerクラス内に定義されているstaticなインナークラスです。
で、このIntegerCacheのJavadocを見てみると、キャッシュする最大値を(大きくする方向にだけ)変更できるようです。
キャッシュする最大値を変更するには以下のようにVMオプションを設定します。
-XX:AutoBoxCacheMax=365
このオプションを設定して以下を実行してみます。

Integer a = 365;
Integer b = 365;
System.out.println(a == b);

結果は true となります。(366でやるとちゃんとfalseになりました)

まとめ

別物に見えたIntegerインスタンスは、実はキャッシュされた同一のインスタンスだったため同値と判定されていた、ということでした。
とはいっても、同値判定でなく等価を判定すべきところではequals()メソッドでの比較に修正するよう話してみようと思っています。
ちなみに、Java9以降ではIntegerのコンストラクタは非推奨となり基本的にvalueOf()で生成するよう推奨されています。

ところで、オートボクシングを実行している処理はどこに実装されているんだろう?