Weird Integer ชกมวยใน 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: เนื่องจากวัตถุทั้งสองเป็นวัตถุที่แยกจากกันดังนั้นจึง==เปรียบเทียบการอ้างอิง แต่ฉันไม่สามารถคิดออกทำไมคำสั่งที่สองกลับมาtrue? มีกฎการเขียนกล่องอัตโนมัติแปลก ๆ ที่เริ่มต้นเมื่อค่าจำนวนเต็มอยู่ในช่วงที่กำหนดหรือไม่? เกิดอะไรขึ้นที่นี่?


1
ดูเหมือนการหลอกลวงstackoverflow.com/questions/1514910/…

3
@RC - ไม่ใช่คนหลอกลวง แต่มีการพูดถึงสถานการณ์ที่คล้ายกัน ขอบคุณสำหรับข้อมูลอ้างอิง
Joel

2
นี่มันแย่มาก นี่คือเหตุผลที่ฉันไม่เคยเข้าใจจุดของดั้งเดิมทั้งหมด แต่เป็นวัตถุ แต่ทั้งสองอย่าง แต่เป็นแบบกล่องอัตโนมัติ แต่ขึ้นอยู่กับ แต่ aaaaaaaaargh
njzk2

1
@Razib: คำว่า "autoboxing" ไม่ใช่รหัสดังนั้นอย่าจัดรูปแบบตามนั้น
ทอม

คำตอบ:


103

trueบรรทัดมีการประกันจริงโดยสเปคภาษา จากหัวข้อ 5.1.7 :

หากค่า p ที่อยู่ในช่องเป็นจริงเท็จไบต์อักขระในช่วง \ u0000 ถึง \ u007f หรือจำนวน int หรือสั้นระหว่าง -128 ถึง 127 ให้ r1 และ r2 เป็นผลลัพธ์ของการแปลงชกมวยสองรายการ ของ p. มักเป็นกรณีที่ r1 == r2

การอภิปรายดำเนินต่อไปโดยแนะนำว่าแม้ว่าบรรทัดที่สองของคุณจะได้รับการรับประกัน แต่บรรทัดแรกจะไม่เป็นเช่นนั้น (ดูย่อหน้าสุดท้ายที่ยกมาด้านล่าง):

ตามหลักการแล้วการชกมวยค่าดั้งเดิม p ที่กำหนดจะให้การอ้างอิงที่เหมือนกันเสมอ ในทางปฏิบัติสิ่งนี้อาจไม่สามารถทำได้โดยใช้เทคนิคการใช้งานที่มีอยู่ กฎข้างต้นเป็นการประนีประนอมในทางปฏิบัติ ประโยคสุดท้ายข้างต้นกำหนดให้ใส่ค่าส่วนกลางบางค่าลงในวัตถุที่แยกไม่ออกเสมอ การใช้งานอาจแคชสิ่งเหล่านี้อย่างเกียจคร้านหรือกระตือรือร้น

สำหรับค่าอื่น ๆ การกำหนดนี้ไม่ใช้สมมติฐานใด ๆ เกี่ยวกับเอกลักษณ์ของค่าแบบบรรจุกล่องในส่วนของโปรแกรมเมอร์ สิ่งนี้จะอนุญาต (แต่ไม่จำเป็นต้องใช้) การแบ่งปันข้อมูลอ้างอิงเหล่านี้บางส่วนหรือทั้งหมด

สิ่งนี้ทำให้มั่นใจได้ว่าในกรณีทั่วไปพฤติกรรมจะเป็นไปตามที่ต้องการโดยไม่กำหนดโทษประสิทธิภาพที่ไม่เหมาะสมโดยเฉพาะในอุปกรณ์ขนาดเล็ก การใช้งานที่ จำกัด หน่วยความจำน้อยลงตัวอย่างเช่นอาจแคชอักขระและกางเกงขาสั้นทั้งหมดรวมทั้งจำนวนเต็มและค่าความยาวในช่วง -32K - + 32K


17
นอกจากนี้ยังอาจเป็นที่น่าสังเกตว่าการทำ autoboxing นั้นเป็นเพียงแค่น้ำตาลในการสังเคราะห์สำหรับการเรียกvalueOfเมธอดของคลาสกล่อง (เช่นInteger.valueOf(int)) น่าสนใจที่ JLS กำหนดการแยกกล่องที่แน่นอนโดยใช้intValue()et al - แต่ไม่ใช่การแยกมวย
gustafc

@gustafc ไม่มีทางอื่นที่จะ unbox Integerกว่าผ่านทางเว็บไซต์อย่างเป็นทางการpublicของ API intValue()โทรคือ แต่มีวิธีอื่นที่เป็นไปได้ในการรับIntegerอินสแตนซ์สำหรับintค่าเช่นคอมไพเลอร์อาจสร้างการเก็บโค้ดและนำIntegerอินสแตนซ์ที่สร้างไว้ก่อนหน้านี้กลับมาใช้ใหม่
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);
   }
}

เอาท์พุท:

false
true

ใช่เอาต์พุตแรกถูกสร้างขึ้นเพื่อเปรียบเทียบการอ้างอิง 'a' และ 'b' - เป็นการอ้างอิงที่แตกต่างกันสองรายการ ในจุดที่ 1 มีการสร้างการอ้างอิงสองรายการซึ่งคล้ายกับ -

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

เอาต์พุตที่สองถูกสร้างขึ้นเนื่องจากJVMพยายามที่จะบันทึกหน่วยความจำเมื่อIntegerตกอยู่ในช่วง (จาก -128 ถึง 127) ณ จุดที่ 2 ไม่มีการสร้างการอ้างอิงใหม่ของประเภทจำนวนเต็มสำหรับ 'd' แทนที่จะสร้างออบเจ็กต์ใหม่สำหรับตัวแปรอ้างอิงชนิดจำนวนเต็ม 'd' จะกำหนดเฉพาะกับออบเจ็กต์ที่สร้างขึ้นก่อนหน้านี้ซึ่งอ้างอิงโดย 'c' JVMทั้งหมดเหล่านี้จะทำโดย

กฎการประหยัดหน่วยความจำเหล่านี้ไม่ได้มีไว้สำหรับ Integer เท่านั้น สำหรับวัตถุประสงค์ในการประหยัดหน่วยความจำสองอินสแตนซ์ของวัตถุ Wrapper ต่อไปนี้ (ในขณะที่สร้างผ่านการชกมวย) จะเป็น == โดยที่ค่าดั้งเดิมเหมือนกันเสมอ -

  • บูลีน
  • ไบต์
  • อักขระตั้งแต่\ u0000ถึง\u007f(7f คือ 127 ในทศนิยม)
  • Short และ Integer จาก-128ถึง127

2
Longยังมีแคชที่มีช่วงเดียวกับInteger.
Eric Wang

8

วัตถุจำนวนเต็มในบางช่วง (ฉันคิดว่าอาจจะ -128 ถึง 127) ได้รับแคชและใช้ซ้ำ จำนวนเต็มนอกช่วงนั้นจะได้รับวัตถุใหม่ทุกครั้ง


1
ช่วงนี้สามารถขยายได้โดยใช้java.lang.Integer.IntegerCache.highคุณสมบัติ น่าสนใจที่ Long ไม่มีตัวเลือกนั้น
Aleksandr Kravets

5

ใช่มีกฎการล้างข้อมูลอัตโนมัติแปลก ๆ ที่เริ่มต้นเมื่อค่าอยู่ในช่วงที่กำหนด เมื่อคุณกำหนดค่าคงที่ให้กับตัวแปร Object ไม่มีสิ่งใดในข้อกำหนดของภาษาที่ระบุว่าต้องสร้างออบเจ็กต์ใหม่ มันอาจนำวัตถุที่มีอยู่กลับมาใช้ใหม่จากแคช

ในความเป็นจริง JVM มักจะจัดเก็บแคชของจำนวนเต็มขนาดเล็กเพื่อจุดประสงค์นี้รวมทั้งค่าต่างๆเช่น Boolean.TRUE และ Boolean.FALSE


4

ฉันเดาว่า Java เก็บแคชของจำนวนเต็มขนาดเล็กที่ 'บรรจุกล่อง' ไว้แล้วเนื่องจากเป็นจำนวนมากและช่วยประหยัดเวลาในการใช้วัตถุที่มีอยู่ซ้ำได้มากกว่าการสร้างใหม่


4

นั่นคือประเด็นที่น่าสนใจ ในหนังสือEffective Javaแนะนำให้ลบล้างเท่ากับสำหรับคลาสของคุณเองเสมอ นอกจากนี้ในการตรวจสอบความเท่าเทียมกันสำหรับสองอินสแตนซ์อ็อบเจ็กต์ของคลาส java ให้ใช้เมธอด 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 ขอหัวข้ออื่น ๆ ไม่ใช่ความเท่าเทียมกันของจำนวนเต็ม แต่เป็นพฤติกรรมรันไทม์ของวัตถุ
Iliya Kuznetsov

3

ใน Java การชกมวยทำงานในช่วงระหว่าง -128 ถึง 127 สำหรับจำนวนเต็ม เมื่อคุณใช้ตัวเลขในช่วงนี้คุณสามารถเปรียบเทียบกับตัวดำเนินการ == สำหรับวัตถุจำนวนเต็มนอกช่วงคุณต้องใช้เท่ากับ


3

การกำหนด 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);
 }

ดังนั้นแทนที่จะสร้างและส่งคืนอ็อบเจ็กต์จำนวนเต็มใหม่Integer.valueOf()เมธอดจะส่งคืนอ็อบเจ็กต์จำนวนเต็มจากภายในIntegerCacheหากลิเทอรัล int ที่ผ่านมีค่ามากกว่า -128 และน้อยกว่า 127

Java แคชอ็อบเจ็กต์จำนวนเต็มเหล่านี้เนื่องจากช่วงของจำนวนเต็มนี้ถูกใช้มากในการเขียนโปรแกรมแบบวันต่อวันซึ่งจะช่วยประหยัดหน่วยความจำทางอ้อม

แคชจะเริ่มต้นในการใช้งานครั้งแรกเมื่อคลาสถูกโหลดลงในหน่วยความจำเนื่องจากบล็อกแบบคงที่ ช่วงสูงสุดของแคชสามารถควบคุมได้โดย-XX:AutoBoxCacheMaxตัวเลือก JVM

ลักษณะการทำงานของแคชนี้ไม่สามารถใช้ได้กับอ็อบเจ็กต์จำนวนเต็มเท่านั้นคล้ายกับ Integer InterCache ที่เรามีByteCache, ShortCache, LongCache, CharacterCacheให้Byte, Short, Long, Characterตามลำดับ

คุณสามารถอ่านเพิ่มเติมเกี่ยวกับบทความของฉันJava จำนวนเต็มแคช - ทำไม Integer.valueOf (127) == Integer.valueOf (127) เป็นจริง


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) จึงเป็นอ็อบเจ็กต์ที่อ้างอิงเดียวกันในระหว่างการทำ autoboxing และเราจะเห็นว่ามีคลาสIntegerCacheดูแลIntegerอาร์เรย์แคชซึ่งเป็นคลาสภายในแบบคงที่ส่วนตัวของIntegerคลาส

มีอีกตัวอย่างที่น่าสนใจที่อาจช่วยให้เราเข้าใจสถานการณ์แปลก ๆ นี้:

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.