Tinju Integer Aneh di Jawa

114

Saya baru saja melihat kode yang mirip dengan ini:

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

Saat dijalankan, blok kode ini akan dicetak:

false
true

Saya mengerti mengapa yang pertama adalah false: karena kedua objek tersebut adalah objek yang terpisah, jadi ==membandingkan referensi. Tapi saya tidak tahu, mengapa pernyataan kedua kembali true? Apakah ada aturan autoboxing aneh yang berlaku saat nilai Integer berada dalam kisaran tertentu? Apa yang terjadi di sini?

Joel
sumber
3
@ RC - Bukan penipuan, tetapi situasi serupa dibahas. Terima kasih untuk referensinya.
Joel
2
ini mengerikan. ini sebabnya saya tidak pernah mengerti maksud dari keseluruhan primitif, tapi objek, tapi keduanya, tapi auto-boxed, tapi tergantung, tapi aaaaaaaaargh.
njzk2
1
@Razib: Kata "autoboxing" bukanlah kode, jadi jangan format seperti itu.
Tom

Jawaban:

102

The truegaris sebenarnya dijamin oleh spesifikasi bahasa. Dari bagian 5.1.7 :

Jika nilai p yang dikotakkan benar, salah, satu byte, karakter dalam rentang \ u0000 hingga \ u007f, atau angka int atau pendek antara -128 dan 127, maka biarkan r1 dan r2 menjadi hasil dari dua konversi tinju dari p. Itu selalu menjadi kasus yang r1 == r2.

Diskusi berlanjut, menunjukkan bahwa meskipun baris kedua keluaran Anda dijamin, yang pertama tidak (lihat paragraf terakhir yang dikutip di bawah):

Idealnya, memasukkan nilai primitif p, akan selalu menghasilkan referensi yang identik. Dalam praktiknya, ini mungkin tidak layak menggunakan teknik implementasi yang ada. Aturan di atas adalah kompromi pragmatis. Klausa terakhir di atas mensyaratkan bahwa nilai-nilai umum tertentu selalu dikotakkan menjadi objek yang tidak dapat dibedakan. Implementasinya mungkin menyimpan cache ini, dengan malas atau bersemangat.

Untuk nilai lain, formulasi ini melarang asumsi apapun tentang identitas nilai kotak di pihak programmer. Ini akan memungkinkan (tetapi tidak mengharuskan) berbagi beberapa atau semua referensi ini.

Ini memastikan bahwa dalam kebanyakan kasus umum, perilaku akan menjadi yang diinginkan, tanpa mengenakan penalti kinerja yang tidak semestinya, terutama pada perangkat kecil. Implementasi dengan memori yang lebih sedikit mungkin, misalnya, menyimpan cache semua karakter dan short, serta integer dan long dalam kisaran -32K - + 32K.

Jon Skeet
sumber
17
Perlu juga dicatat bahwa autoboxing sebenarnya hanyalah gula sintaksis untuk memanggil valueOfmetode kelas kotak (seperti Integer.valueOf(int)). Menarik bahwa JLS mendefinisikan desugaring unboxing yang tepat - menggunakan intValue()dkk - tetapi bukan desugaring tinju.
gustafc
@gustafc tidak ada cara lain untuk membuka kotak Integerselain melalui publicAPI resmi , yaitu dengan menelepon intValue(). Tetapi ada cara lain yang mungkin untuk mendapatkan sebuah Integerinstance untuk suatu intnilai, misalnya kompiler dapat menghasilkan penyimpanan kode dan menggunakan kembali Integerinstance yang dibuat sebelumnya .
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);
   }
}

Keluaran:

false
true

Ya, keluaran pertama diproduksi untuk membandingkan referensi; 'a' dan 'b' - ini adalah dua referensi yang berbeda. Pada poin 1, sebenarnya dibuat dua referensi yang mirip dengan -

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

Keluaran kedua dihasilkan karena JVMmencoba untuk menghemat memori, ketika Integerberada dalam kisaran (dari -128 hingga 127). Pada poin 2 tidak ada referensi baru untuk tipe Integer dibuat untuk 'd'. Alih-alih membuat objek baru untuk variabel referensi tipe Integer 'd', itu hanya ditugaskan dengan objek yang dibuat sebelumnya direferensikan oleh 'c'. Semua ini dilakukan oleh JVM.

Aturan penyimpanan memori ini tidak hanya untuk Integer. untuk tujuan penyimpanan memori, dua contoh objek pembungkus berikut (saat dibuat melalui tinju), akan selalu == di mana nilai primitifnya sama -

  • Boolean
  • Byte
  • Karakter dari \ u0000 sampai \u007f(7f adalah 127 dalam desimal)
  • Pendek dan Integer dari -128 hingga 127
Razib
sumber
2
Longjuga memiliki cache dengan kisaran yang sama seperti Integer.
Eric Wang
8

Objek bilangan bulat dalam beberapa kisaran (saya pikir mungkin -128 hingga 127) di-cache dan digunakan kembali. Bilangan bulat di luar rentang tersebut mendapatkan objek baru setiap kali.

Adam Crume
sumber
1
Kisaran ini dapat diperpanjang dengan menggunakan java.lang.Integer.IntegerCache.highproperti. Menarik bahwa Long tidak memiliki opsi itu.
Aleksandr Kravets
5

Ya, ada aturan autoboxing aneh yang berlaku saat nilai berada dalam kisaran tertentu. Saat Anda menetapkan konstanta ke variabel Objek, tidak ada dalam definisi bahasa yang mengatakan objek baru harus dibuat. Ini dapat menggunakan kembali objek yang ada dari cache.

Faktanya, JVM biasanya akan menyimpan cache dari Integer kecil untuk tujuan ini, serta nilai-nilai seperti Boolean.TRUE dan Boolean.FALSE.

Avi
sumber
4

Dugaan saya adalah bahwa Java menyimpan cache bilangan bulat kecil yang sudah 'dikotak' karena sangat umum dan menghemat banyak waktu untuk menggunakan kembali objek yang ada daripada membuat yang baru.

Beraneka ragam
sumber
4

Itu adalah hal yang menarik. Dalam buku Java Efektif menyarankan untuk selalu menimpa sama dengan kelas Anda sendiri. Juga, untuk memeriksa kesetaraan untuk dua contoh objek dari kelas java selalu gunakan metode sama.

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

kembali:

true
true
AmirHd
sumber
@ Joel meminta topik yang sangat lain, bukan persamaan Integer tetapi perilaku runtime objek.
Iliya Kuznetsov
3

Di Java tinju bekerja dalam kisaran antara -128 dan 127 untuk Integer. Saat Anda menggunakan angka dalam rentang ini, Anda dapat membandingkannya dengan operator ==. Untuk objek Integer di luar rentang Anda harus menggunakan sama.

marvin
sumber
3

Penetapan langsung literal int ke referensi Integer adalah contoh auto-boxing, di mana nilai literal ke kode konversi objek ditangani oleh kompilator.

Jadi selama fase kompilasi, kompilator diubah Integer a = 1000, b = 1000;menjadi Integer a = Integer.valueOf(1000), b = Integer.valueOf(1000);.

Jadi ini adalah Integer.valueOf()metode yang benar-benar memberi kita objek integer, dan jika kita melihat kode sumber Integer.valueOf()metode kita dapat dengan jelas melihat metode cache objek integer dalam kisaran -128 hingga 127 (inklusif).

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

Jadi, alih-alih membuat dan mengembalikan objek integer baru, Integer.valueOf()metode ini mengembalikan objek Integer dari internal IntegerCachejika literal int yang diteruskan lebih besar dari -128 dan kurang dari 127.

Java menyimpan cache objek integer ini karena kisaran integer ini banyak digunakan dalam pemrograman sehari-hari yang secara tidak langsung menghemat beberapa memori.

Cache diinisialisasi pada penggunaan pertama saat kelas dimuat ke memori karena blok statis. Kisaran maksimal cache dapat dikontrol dengan -XX:AutoBoxCacheMaxopsi JVM.

Perilaku caching ini tidak berlaku untuk objek Integer saja, mirip dengan Integer.IntegerCache yang juga kami miliki ByteCache, ShortCache, LongCache, CharacterCacheuntuk Byte, Short, Long, Charactermasing - masing.

Anda dapat membaca lebih lanjut di artikel saya Java Integer Cache - Why Integer.valueOf (127) == Integer.valueOf (127) Is True .

Naresh Joshi
sumber
0

Di Java 5, fitur baru diperkenalkan untuk menghemat memori dan meningkatkan kinerja penanganan objek tipe Integer. Objek integer di-cache secara internal dan digunakan kembali melalui objek referensi yang sama.

  1. Ini berlaku untuk nilai Integer dalam rentang antara –127 hingga +127 (Nilai Integer Maks).

  2. Caching Integer ini hanya berfungsi pada autoboxing. Objek integer tidak akan di-cache saat dibuat menggunakan konstruktor.

Untuk detail lebih lanjut, silakan buka Tautan di bawah ini:

Cache Integer secara Detail

Rahul Maurya
sumber
0

Jika kita memeriksa kode sumber Integerobjek, kita akan menemukan sumber valueOfmetode seperti ini:

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

yang bisa menjelaskan mengapa Integerobjek, yang dalam rentang dari -128 ( Integer.low) hingga 127 ( Integer.high), adalah objek referensi yang sama selama autoboxing. Dan kita bisa melihat ada kelas yang IntegerCachemenangani Integerarray cache, yang merupakan kelas dalam kelas statis pribadi Integer.

Ada contoh menarik lainnya yang dapat membantu kita memahami situasi aneh ini:

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    

}
L Joey
sumber