Javaの奇妙な整数ボクシング


114

私はちょうどこれに似たコードを見た:

public class Scratch
{
    public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;
        System.out.println(a == b);

        Integer c = 100, d = 100;
        System.out.println(c == d);
    }
}

実行すると、次のコードブロックが出力されます。

false
true

最初の理由がわかりfalseます。2つのオブジェクトが別々のオブジェクトであるため、==は参照を比較します。しかし、私は理解できません、なぜ2番目のステートメントが返されるのtrueですか?Integerの値が特定の範囲にあるときに実行される奇妙なオートボクシングルールはありますか?何が起きてる?



3
@RC-かなりまともではありませんが、同様の状況が議論されています。参考にしてくれてありがとう。
ジョエル

2
これは恐ろしいことです。これが、私がそのプリミティブ全体の目的を理解できなかった理由です。
njzk2 14

1
@Razib:「オートボクシング」という言葉はコードではないので、そのようにフォーマットしないでください。
トム

回答:


102

このtrue行は、実際には言語仕様によって保証されています。セクション5.1.7から:

ボックス化される値pがtrue、false、バイト、\ u0000から\ u007fの範囲の文字、または-128から127までの整数または短い数値の場合、r1とr2を2つのボックス化変換の結果とするの 常にr1 == r2です。

議論は続いており、出力の2行目は保証されていますが、1行目は保証されていないことを示唆しています(下に引用した最後の段落を参照)。

理想的には、特定のプリミティブ値pをボックス化すると、常に同じ参照が生成されます。実際には、これは既存の実装技術を使用して実現できない場合があります。上記のルールは実用的な妥協です。上記の最後の節では、特定の一般的な値を常に区別できないオブジェクトにボックス化する必要があります。実装では、これらを遅延して、または積極的にキャッシュできます。

他の値の場合、この定式化は、プログラマー側のボックス化された値の同一性に関する想定を許可しません。これにより、これらの参照の一部またはすべてを共有できるようになります(必須ではありません)。

これにより、ほとんどの一般的なケースで、特に小さなデバイスで、過度のパフォーマンスペナルティを課すことなく、動作が望ましい動作になります。メモリの制限が少ない実装では、たとえば、すべての文字とshortだけでなく、-32K〜+ 32Kの範囲の整数とlongもキャッシュする可能性があります。


17
また、オートボクシングは実際valueOfにはボックスクラスのメソッドを呼び出すための単なる構文上の糖衣であることにも注意してください(などInteger.valueOf(int))。興味深いことに、JLSは、ボクシングの脱糖ではなく、正確な開梱の脱糖を定義していintValue()ます。
gustafc 2010年

@gustafcを開梱する方法はInteger、公式publicAPIを使用する以外にありませんintValue()。つまり、を呼び出します。ただしIntegerint値のインスタンスを取得する方法は他にもあります。たとえば、コンパイラは、以前に作成したIntegerインスタンスを保持して再利用するコードを生成する場合があります。
ホルガー

31
public class Scratch
{
   public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;  //1
        System.out.println(a == b);

        Integer c = 100, d = 100;  //2
        System.out.println(c == d);
   }
}

出力:

false
true

はい、最初の出力は参照を比較するために生成されます。「a」と「b」-これらは2つの異なる参照です。ポイント1では、実際には次のような2つの参照が作成されます-

Integer a = new Integer(1000);
Integer b = new Integer(1000);

2番目の出力はJVMIntegerが範囲(-128から127)にあるときにメモリを節約しようとするために生成されます。ポイント2では、「d」に対してInteger型の新しい参照は作成されません。Integer型の参照変数「d」の新しいオブジェクトを作成する代わりに、「c」が参照する以前に作成されたオブジェクトにのみ割り当てられます。これらはすべてによって行われJVMます。

これらのメモリ節約ルールはIntegerだけのものではありません。メモリを節約するために、次のラッパーオブジェクトの2つのインスタンス(ボクシングで作成されたもの)は常に==となり、プリミティブ値は同じです-

  • ブール
  • バイト
  • \ u0000から\u007f(7fは10進数で127)の文字
  • -128から127までの短整数

2
Longと同じ範囲のキャッシュもありIntegerます。
Eric Wang

8

ある範囲の整数オブジェクト(おそらく-128から127まで)はキャッシュされ、再利用されます。その範囲外の整数は、毎回新しいオブジェクトを取得します。


1
この範囲は、java.lang.Integer.IntegerCache.highプロパティを使用して拡張できます。興味深いことに、Longにはそのオプションはありません。
Aleksandr Kravets 2015

5

はい、値が特定の範囲内にあるときに実行される奇妙なオートボクシングルールがあります。定数をObject変数に割り当てる場合、言語定義では、新しいオブジェクトを作成する必要があることを示していません。キャッシュから既存のオブジェクトを再利用する場合があります。

実際、JVMは通常、この目的のために小さな整数のキャッシュと、Boolean.TRUEやBoolean.FALSEなどの値を格納します。


4

私の推測では、Javaはすでに「ボックス化」されている小さな整数のキャッシュを保持しているため、それらは非常に一般的であり、新しいオブジェクトを作成するよりも既存のオブジェクトを再利用する方が多くの時間を節約できます。


4

それは興味深い点です。この本では、Effective Javaは常に自分のクラスのequalsをオーバーライドすることを推奨しています。また、Javaクラスの2つのオブジェクトインスタンスの等価性をチェックするには、常にequalsメソッドを使用します。

public class Scratch
{
    public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;
        System.out.println(a.equals(b));

        Integer c = 100, d = 100;
        System.out.println(c.equals(d));
    }
}

戻り値:

true
true

@Joelは、整数の平等ではなく、オブジェクトの実行時の動作について、非常に他のトピックを求めました。
イリヤクズネツォフ

3

Javaでは、ボクシングは整数では-128から127の範囲で機能します。この範囲の数値を使用している場合は、==演算子と比較できます。範囲外のIntegerオブジェクトの場合は、equalsを使用する必要があります。


3

Integer参照へのintリテラルの直接割り当ては自動ボックス化の例であり、オブジェクト変換コードへのリテラル値はコンパイラーによって処理されます。

したがって、コンパイル段階でコンパイラはに変換さInteger a = 1000, b = 1000;Integer a = Integer.valueOf(1000), b = Integer.valueOf(1000);ます。

つまり、Integer.valueOf()実際に整数オブジェクトを提供するのはメソッドであり、Integer.valueOf()メソッドのソースコードを見ると、メソッドが-128〜127(両端を含む)の範囲の整数オブジェクトをキャッシュしていることがはっきりとわかります。

/**
 *
 * This method will always cache values in the range -128 to 127,
 * inclusive, and may cache other values outside of this range.
 *
 * @param  i an {@code int} value.
 * @return an {@code Integer} instance representing {@code i}.
 * @since  1.5
 */
 public static Integer valueOf(int i) {
     if (i >= IntegerCache.low && i <= IntegerCache.high)
         return IntegerCache.cache[i + (-IntegerCache.low)];
     return new Integer(i);
 }

そのため、渡されたintリテラルが-128より大きく127より小さい場合Integer.valueOf()、メソッドは新しい整数オブジェクトを作成して返す代わりに、内部からIntegerオブジェクトを返しますIntegerCache

Javaはこれらの整数オブジェクトをキャッシュします。これは、この整数の範囲が間接的に一部のメモリを節約するプログラミングで日常的に使用されるためです。

静的ブロックのためにクラスがメモリに読み込まれると、最初の使用時にキャッシュが初期化されます。キャッシュの最大範囲は、-XX:AutoBoxCacheMaxJVMオプションで制御できます。

整数は、我々はまた、持っているInteger.IntegerCacheと同様に、オブジェクトだけのためにこのキャッシュの動作は適用されないByteCache, ShortCache, LongCache, CharacterCacheためByte, Short, Long, Character、それぞれ。

詳細については、私の記事「Java Integer Cache-Why Integer.valueOf(127)== Integer.valueOf(127)Is True」を参照してください


0

Java 5では、メモリを節約し、整数型オブジェクトの処理のパフォーマンスを向上させる新機能が導入されました。整数オブジェクトは内部的にキャッシュされ、同じ参照オブジェクトを介して再利用されます。

  1. これは、-127〜+127の範囲の整数値(最大整数値)に適用されます。

  2. この整数キャッシュは、オートボクシングでのみ機能します。整数オブジェクトは、コンストラクターを使用して構築されるときにキャッシュされません。

詳細については、以下のリンクを参照してください:

整数キャッシュの詳細


0

Integerobeject のソースコードを確認すると、次のvalueOfようにメソッドのソースが見つかります。

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

Integer-128(Integer.low)から127(Integer.high)の範囲のオブジェクトがオートボクシング中に参照されるオブジェクトと同じである理由を説明できます。そして、クラスのプライベート静的内部クラスであるキャッシュ配列を処理するクラスIntegerCacheがあることがわかります。IntegerInteger

この奇妙な状況を理解するのに役立つ別の興味深い例があります。

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {

      Class cache = Integer.class.getDeclaredClasses()[0]; 
      Field myCache = cache.getDeclaredField("cache"); 
      myCache.setAccessible(true);

      Integer[] newCache = (Integer[]) myCache.get(cache); 
      newCache[132] = newCache[133]; 

      Integer a = 2;
      Integer b = a + a;
      System.out.printf("%d + %d = %d", a, a, b); //The output is: 2 + 2 = 5    

}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.