Java'da Garip Tamsayı Boks


114

Az önce şuna benzer bir kod gördüm:

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);
    }
}

Çalıştırıldığında, bu kod bloğu yazdırılacaktır:

false
true

Birincisinin neden olduğunu anlıyorum false: çünkü iki nesne ayrı nesnelerdir, bu nedenle ==referansları karşılaştırır. Ama anlayamıyorum, ikinci ifade neden geri dönüyor true? Bir Tamsayı'nın değeri belirli bir aralıkta olduğunda devreye giren garip bir otomatik kutulama kuralı var mı? Burada neler oluyor?



3
@RC - Tam bir dupe değil ama benzer bir durum tartışılıyor. Yine de referans için teşekkürler.
Joel

2
bu korkunç. bu yüzden, bütün o ilkelin amacını asla anlamadım, ama nesneyi, ama her ikisini de, ama otomatik kutulu, ama bağlıdır, ama aaaaaaaaargh.
njzk2

1
@Razib: "Otomatik kutulama" kelimesi kod değildir, bu yüzden onu bu şekilde biçimlendirmeyin.
Tom

Yanıtlar:


103

trueÇizgi aslında dil spesifikasyonu tarafından garanti edilmektedir. Gönderen bölüm 5.1.7 :

Kutulu p değeri true, false, bir bayt, \ u0000 - \ u007f aralığında bir karakter veya -128 ile 127 arasında bir int veya kısa sayı ise, r1 ve r2 herhangi iki boks dönüşümünün sonucu olsun s. Her zaman r1 == r2 durumudur.

Tartışma devam ediyor ve ikinci çıktı satırınız garantili olmasına rağmen, ilkinin garanti edilmediğini öne sürüyor (aşağıda alıntı yapılan son paragrafa bakın):

İdeal olarak, belirli bir ilkel değeri kutulamak p, her zaman aynı referans verir. Uygulamada, mevcut uygulama tekniklerini kullanarak bu mümkün olmayabilir. Yukarıdaki kurallar pragmatik bir uzlaşmadır. Yukarıdaki son cümle, belirli ortak değerlerin her zaman ayırt edilemeyen nesneler içine alınmasını gerektirir. Uygulama bunları tembel veya hevesle önbelleğe alabilir.

Diğer değerler için, bu formülasyon, programcı tarafında kutulu değerlerin kimliği hakkında herhangi bir varsayıma izin vermez. Bu, bu referansların bir kısmının veya tamamının paylaşılmasına izin verir (ancak gerektirmez).

Bu, çoğu yaygın durumda, özellikle küçük cihazlarda aşırı bir performans cezası olmaksızın davranışın istenen davranış olmasını sağlar. Daha az bellek sınırlı uygulamalar, örneğin, -32K - + 32K aralığındaki tüm karakterleri ve kısa tam sayıları ve uzunları önbelleğe alabilir.


17
Otomatik kutulamanın aslında valueOfkutu sınıfının (gibi Integer.valueOf(int)) yöntemini çağırmak için sadece sözdizimsel şeker olduğunu da belirtmek gerekir . İlginçtir ki, JLS, - intValue()ve diğerlerini kullanarak - ama boks şekerleme çözümünü tanımlamaz.
gustafc

@gustafc, kutuyu açmanın Integerresmi publicAPI yoluyla , yani çağırmaktan başka bir yolu yoktur intValue(). Ancak Integerbir intdeğer için örnek almanın başka olası yolları da vardır , örneğin bir derleyici önceden oluşturulmuş Integerörnekleri saklayarak ve yeniden kullanarak kod üretebilir .
Holger

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);
   }
}

Çıktı:

false
true

Evet, ilk çıktı referansı karşılaştırmak için üretilir; 'a' ve 'b' - bunlar iki farklı referanstır. Birinci noktada, aslında aşağıdakine benzer iki referans oluşturulur:

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

İkinci çıktı, bir aralığa düştüğünde (-128'den 127'ye) JVMbellek kaydetmeye çalıştığı için üretilir Integer. 2. noktada, 'd' için Tamsayı türünde yeni bir referans oluşturulmaz. Tamsayı türü referans değişkeni 'd' için yeni bir nesne oluşturmak yerine, yalnızca 'c' ile başvurulan önceden oluşturulmuş nesne ile atanır. Bütün bunlar tarafından yapılır JVM.

Bu hafıza tasarrufu kuralları sadece Tamsayı için değildir. bellek tasarrufu amacıyla, aşağıdaki sarmalayıcı nesnelerin iki örneği (kutulama yoluyla oluşturulurken), her zaman == ilkel değerlerinin aynı olduğu durumda olacaktır -

  • Boole
  • Bayt
  • Dan Karakter \ u0000 için \u007f(7f ondalık 127 olan)
  • Kısa ve tamsayı -128 için 127

2
Longile aynı aralıkta önbelleğe sahiptir Integer.
Eric Wang

8

Bazı aralıktaki tamsayı nesneleri (belki -128'den 127'ye kadar) önbelleğe alınır ve yeniden kullanılır. Bu aralığın dışındaki tam sayılar her seferinde yeni bir nesne alır.


1
Bu aralık özelliği kullanılarak genişletilebilir java.lang.Integer.IntegerCache.high. Long'un bu seçeneğe sahip olmaması ilginç.
Aleksandr Kravets

5

Evet, değerler belirli bir aralıkta olduğunda devreye giren garip bir otomatik kutulama kuralı var. Bir Object değişkenine bir sabit atadığınızda, dil tanımındaki hiçbir şey yeni bir nesnenin oluşturulması gerektiğini söylemez . Önbellekteki mevcut bir nesneyi yeniden kullanabilir.

Aslında, JVM genellikle bu amaç için küçük Tamsayılardan oluşan bir önbelleğin yanı sıra Boolean.TRUE ve Boolean.FALSE gibi değerleri de depolar.


4

Tahminimce Java, çok yaygın oldukları için zaten 'kutulu' olan küçük tamsayılardan oluşan bir önbellek tutuyor ve mevcut bir nesneyi yeniden kullanmak, yenisini oluşturmaktan çok zaman kazandırıyor.


4

Bu ilginç bir nokta. Etkili Java kitabında , her zaman kendi sınıflarınız için eşitleri geçersiz kılmanızı önerir. Ayrıca, bir java sınıfının iki nesne örneğinin eşitliğini kontrol etmek için her zaman eşittir yöntemini kullanın.

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));
    }
}

İadeler:

true
true

@Joel, Tamsayı eşitliği değil, nesnelerin çalışma zamanı davranışı gibi başka bir konu istedi.
Iliya Kuznetsov

3

Java'da kutulama, bir Tamsayı için -128 ile 127 arasındaki aralıkta çalışır. Bu aralıktaki sayıları kullandığınızda, bunu == operatörüyle karşılaştırabilirsiniz. Aralığın dışındaki Tamsayı nesneler için eşittir kullanmanız gerekir.


3

Bir tamsayı referansına bir int değişmezinin doğrudan atanması, nesne dönüştürme kodunun değişmez değerinin derleyici tarafından işlendiği bir otomatik kutulama örneğidir.

Derleme aşaması derleyici dönüştürür sırasında Yani Integer a = 1000, b = 1000;için Integer a = Integer.valueOf(1000), b = Integer.valueOf(1000);.

Dolayısıyla Integer.valueOf()bize tamsayı nesnelerini veren Integer.valueOf()yöntemdir ve yöntemin kaynak koduna bakarsak, yöntemin -128 ila 127 (dahil) aralığında tam sayı nesneleri önbelleğe aldığını açıkça görebiliriz.

/**
 *
 * 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);
 }

Dolayısıyla, yeni tamsayı nesneleri oluşturmak ve döndürmek yerine , iletilen int değişmez değeri -128'den büyük ve 127'den küçükse Integer.valueOf(), yöntem dahili nesneden Tamsayı nesneleri döndürür IntegerCache.

Java, bu tamsayı nesnelerini önbelleğe alır çünkü bu tamsayı aralığı, günlük programlama için çok kullanılır ve bu da dolaylı olarak biraz bellek tasarrufu sağlar.

Önbellek, statik blok nedeniyle sınıf belleğe yüklendiğinde ilk kullanımda başlatılır. Maksimum önbellek aralığı -XX:AutoBoxCacheMaxJVM seçeneği ile kontrol edilebilir .

Tamsayı biz de var Integer.IntegerCache benzer sadece nesneleri için bu önbelleğe alma davranışı geçerli değildir ByteCache, ShortCache, LongCache, CharacterCacheiçin Byte, Short, Long, Charactersırasıyla.

Java Integer Cache - Why Integer.valueOf (127) == Integer.valueOf (127) Is True makalem hakkında daha fazla bilgi edinebilirsiniz .


0

Java 5'te, hafızayı kaydetmek ve Tamsayı tipi nesne işlemlerinin performansını artırmak için yeni bir özellik tanıtıldı. Tamsayı nesneleri dahili olarak önbelleğe alınır ve aynı referans nesneler aracılığıyla yeniden kullanılır.

  1. Bu, –127 ila +127 aralığındaki Tamsayı değerleri için geçerlidir (Maks. Tamsayı değeri).

  2. Bu Tamsayı önbelleğe alma yalnızca otomatik kutulamada çalışır. Tamsayı nesneleri, yapıcı kullanılarak oluşturulduklarında önbelleğe alınmayacaktır.

Daha fazla ayrıntı için lütfen aşağıdaki bağlantıya gidin:

Ayrıntılı Tamsayı Önbelleği


0

IntegerObeject'in kaynak kodunu kontrol edersek, valueOfyöntemin kaynağını şöyle bulacağız :

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

Bu, Integer-128 ( Integer.low) ila 127 ( Integer.high) aralığındaki nesnelerin otomatik kutulama sırasında neden aynı referans nesneler olduğunu açıklayabilir. Ve bir sınıfın IntegerCache, Integerözel bir statik iç sınıf sınıfı olan önbellek dizisiyle ilgilendiğini görebiliriz Integer.

Bu tuhaf durumu anlamamıza yardımcı olabilecek başka ilginç bir örnek daha var:

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    

}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.