ทำไมการเปรียบเทียบ == กับ Integer.valueOf (String) จึงให้ผลลัพธ์ที่แตกต่างกันสำหรับ 127 และ 128


182

ฉันไม่รู้ว่าเพราะเหตุใดบรรทัดโค้ดเหล่านี้จึงส่งกลับค่าที่ต่างกัน:

System.out.println(Integer.valueOf("127")==Integer.valueOf("127"));
System.out.println(Integer.valueOf("128")==Integer.valueOf("128"));
System.out.println(Integer.parseInt("128")==Integer.valueOf("128"));

ผลลัพธ์คือ:

true
false
true

ทำไมคนแรกถึงกลับมาtrueและคนที่สองกลับมาfalse? มีอะไรที่แตกต่างที่ฉันไม่รู้ระหว่าง127และ128? (แน่นอนฉันรู้ว่า127< 128.)

นอกจากนี้ทำไมหนึ่งในสามกลับมาtrue?

ฉันได้อ่านคำตอบของคำถามนี้แล้ว แต่ฉันยังไม่เข้าใจว่ามันจะกลับมาได้trueอย่างไรและทำไมโค้ดในบรรทัดที่สองจึงกลับfalseมา


6
จำนวนเต็มเป็นวัตถุ หากคุณต้องการเปรียบเทียบเพื่อความเท่าเทียมใช้.equals()มิฉะนั้นการเดิมพันทั้งหมดจะปิด
Karl Damgaard Asmussen

6
@KarlDamgaardAsmussen จริง ๆ แล้วที่นี่ฉันต้องการทดสอบว่าพวกเขาอ้างอิงถึงวัตถุเดียวกันหรือไม่และในตอนแรกฉันไม่เข้าใจว่าทำไม 127 128 ให้ผลลัพธ์ที่แตกต่าง
DnR

@DnR หาก Java เป็นภาษาที่มีข้อกำหนดมาตรฐานฉันจะคิดว่ามันปล่อยให้เรื่องดังกล่าวขึ้นอยู่กับการใช้งานหรือแม้กระทั่งพฤติกรรมที่ไม่ได้กำหนดซึ่งได้รับคำสั่ง
Karl Damgaard Asmussen

1
@jszumski: มีคำถามนี้มากกว่าส่วนแคชเท่านั้น นอกจากนี้คำตอบที่เชื่อมโยงนั้นไม่สมบูรณ์แบบที่สุด - มันไม่ได้ลงรายละเอียดเกี่ยวกับสิ่งที่เก็บไว้และทำไม
Makoto

1
สำหรับการติดตามเพิ่มเติมเกี่ยวกับการสนทนานี้โปรดดูที่โพสต์เมตานี้
Jeroen Vannevel

คำตอบ:


191

มีความแตกต่างที่โดดเด่นที่นี่

valueOfกำลังส่งคืนIntegerวัตถุซึ่งอาจมีค่าของแคชระหว่าง -128 ถึง 127 นี่คือสาเหตุที่ค่าแรกส่งคืนtrue- มันถูกแคช - และค่าที่สองส่งคืนfalse- 128 ไม่ใช่ค่าแคชดังนั้นคุณจึงได้รับสองIntegerอินสแตนซ์แยกกัน.

มันเป็นสิ่งสำคัญที่จะต้องทราบว่าคุณกำลังเปรียบเทียบอ้างอิงด้วยInteger#valueOfและถ้าคุณกำลังเปรียบเทียบค่าที่มีขนาดใหญ่กว่าสิ่งแคชสนับสนุนก็จะไม่ได้ประเมินtrueถึงแม้ว่าค่าแจงเทียบเท่า (กรณีในจุด: Integer.valueOf(128) == Integer.valueOf(128)) คุณต้องใช้equals()แทน

parseIntintจะกลับมาดั้งเดิม นี่คือเหตุผลที่ผลตอบแทนที่คุ้มค่าที่สามtrue- การประเมินและเป็นของหลักสูตร128 == 128true

ทีนี้พอใช้ไปสักหน่อยเพื่อให้ได้ผลลัพธ์ที่สามtrue:

  • แปลง unboxing เกิดขึ้นเกี่ยวกับการดำเนินการความเท่าเทียมที่คุณกำลังใช้และประเภทข้อมูลที่คุณมี - คือและint Integerคุณได้รับIntegerจากvalueOfบนด้านขวามือของหลักสูตร

  • หลังจากการแปลงคุณกำลังเปรียบเทียบintค่าดั้งเดิมสองค่า เปรียบเทียบเกิดขึ้นเช่นเดียวกับที่คุณจะคาดหวังให้ส่วนที่เกี่ยวกับวิทยาการเพื่อให้คุณลมขึ้นเปรียบเทียบและ128128


2
@ user3152527: มีความแตกต่างที่มีขนาดใหญ่เป็น - Listหนึ่งถือว่าเป็นวัตถุซึ่งหมายความว่าคุณสามารถโทรหาวิธีการและโต้ตอบกับมันในโครงสร้างข้อมูลนามธรรมเช่น อีกอันหนึ่งเป็นแบบดั้งเดิมซึ่งเป็นเพียงค่าดิบ
Makoto

1
@ user3152527 คุณถามคำถามที่ยอดเยี่ยม (และไม่ใช่คำถามที่โง่ที่สุด) แต่คุณได้กำหนดให้ใช้. เท่ากับใช่ไหม
user2910265

3
อาดูเหมือนว่าผู้ถามไม่เข้าใจข้อเท็จจริงพื้นฐานใน Java: เมื่อใช้ "==" เพื่อเปรียบเทียบวัตถุสองรายการคุณกำลังทดสอบว่าพวกเขากำลังอ้างอิงไปยังวัตถุเดียวกันหรือไม่ เมื่อใช้ "equals ()" คุณกำลังทดสอบว่าพวกเขามีค่าเท่ากันหรือไม่ คุณไม่สามารถใช้ "เท่ากับ" เพื่อเปรียบเทียบแบบดั้งเดิม
Jay

3
@ ใช่ฉันเข้าใจว่า ==แต่อย่างหนึ่งที่ฉันสับสนในตอนแรกคือเหตุผลที่คนแรกที่กลับมาจริงและเท็จที่สองกลับมาโดยใช้วิธีการเปรียบเทียบเดียวกัน อย่างไรก็ตามมันชัดเจนในขณะนี้
DnR

1
Nit: มันไม่ใช่แค่ว่าจำนวนเต็ม "อาจจะ" ถูกเก็บไว้ระหว่าง -128 และ 127 มันต้องเป็นไปตามJLS 5.1.7 มันอาจจะถูกเก็บไว้นอกช่วงนั้น แต่ไม่จำเป็นต้องเป็น (และมักจะไม่ได้)
yshavit

127

Integerชั้นมีแคชแบบคงที่ร้านค้า 256 พิเศษIntegerวัตถุ - หนึ่งสำหรับค่าระหว่าง -128 และ 127 โดยที่ในใจทุกพิจารณาแตกต่างระหว่างทั้งสาม

new Integer(123);

สิ่งนี้ (ชัด) ทำให้Integerวัตถุใหม่เอี่ยม

Integer.parseInt("123");

นี้ส่งกลับค่าดั้งเดิมหลังจากแยกintString

Integer.valueOf("123");

นี่มันซับซ้อนกว่าตัวอื่น ๆ Stringมันเริ่มต้นปิดโดยการแยก จากนั้นหากค่าอยู่ระหว่าง -128 ถึง 127 จะส่งคืนวัตถุที่สอดคล้องกันจากแคชแบบสแตติก หากค่าอยู่นอกช่วงนี้จะเรียกใช้new Integer()และส่งผ่านค่าเพื่อให้คุณได้รับวัตถุใหม่

ทีนี้ลองพิจารณาสามสำนวนในคำถาม

Integer.valueOf("127")==Integer.valueOf("127");

สิ่งนี้จะคืนค่าจริงเนื่องจากIntegerค่าที่มีคือ 127 จะถูกดึงสองครั้งจากแคชแบบสแตติกและเปรียบเทียบกับตัวมันเอง มีเพียงIntegerวัตถุเดียวที่เกี่ยวข้องดังนั้นการส่งคืนtrueนี้

Integer.valueOf("128")==Integer.valueOf("128");

ผลตอบแทนนี้falseเพราะ 128 ไม่ได้อยู่ในแคชแบบคงที่ ดังนั้นใหม่Integerถูกสร้างขึ้นสำหรับแต่ละด้านของความเท่าเทียมกัน เนื่องจากมีแตกต่างกันสองIntegerวัตถุและ==วัตถุที่ส่งกลับเฉพาะถ้าทั้งสองฝ่ายมีวัตถุเดียวกันนี้เป็นไปได้truefalse

Integer.parseInt("128")==Integer.valueOf("128");

นี่เป็นการเปรียบเทียบintค่าดั้งเดิม128 ทางด้านซ้ายกับIntegerวัตถุที่สร้างขึ้นใหม่ทางด้านขวา แต่เนื่องจากมันไม่สมเหตุสมผลที่จะเปรียบเทียบintกับ an Integer, Java จะทำการ unbox โดยอัตโนมัติIntegerก่อนทำการเปรียบเทียบ; ดังนั้นคุณจะจบลงเมื่อเทียบไปยังint intตั้งแต่ดั้งเดิม 128 trueเท่ากับตัวเองผลตอบแทนนี้


13

ดูแลค่าที่ส่งคืนจากวิธีการเหล่านี้ valueOfวิธีการส่งกลับอินสแตนซ์จำนวนเต็ม:

public static Integer valueOf(int i)

parseIntวิธีการผลตอบแทนที่คุ้มค่าจำนวนเต็ม (ชนิดดั้งเดิม):

public static int parseInt(String s) throws NumberFormatException

คำอธิบายสำหรับการเปรียบเทียบ:

เพื่อบันทึกหน่วยความจำสองอินสแตนซ์ของวัตถุตัวห่อหุ้มจะเป็น == เสมอเมื่อค่าดั้งเดิมของพวกเขาเหมือนกัน:

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

เมื่อ == ถูกใช้เพื่อเปรียบเทียบแบบดั้งเดิมกับ wrapper นั้น wrapper จะไม่ถูกแยกออกและการเปรียบเทียบจะเป็นแบบดั้งเดิมกับแบบดั้งเดิม

ในสถานการณ์ของคุณ (ตามกฎข้างต้น):

Integer.valueOf("127")==Integer.valueOf("127")

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

Integer.valueOf("128")==Integer.valueOf("128")

สำนวนนี้เปรียบเทียบการอ้างอิงไปยังวัตถุที่แตกต่างกันเพราะพวกเขามีค่าจำนวนเต็มไม่ได้อยู่ใน <-128, 127> falseดังนั้นจึงผลตอบแทน

Integer.parseInt("128")==Integer.valueOf("128")

สำนวนนี้เปรียบเทียบค่าดั้งเดิม (ด้านซ้ายมือ) และการอ้างอิงถึงวัตถุ (ด้านขวามือ) trueด้านขวามือดังนั้นจะยังไม่ได้เปิดและชนิดดั้งเดิมของเขาจะนำมาเปรียบเทียบกับที่เหลือจึงเป็นผลตอบแทน


3
คำถามที่คล้ายกัน: stackoverflow.com/questions/9824053/…
piobab

คุณสามารถระบุ URL สำหรับแหล่งที่มาของคำพูดได้หรือไม่?
Philzen

"... สองอินสแตนซ์ของวัตถุตัวห่อหุ้มจะเป็น == เสมอเมื่อค่าดั้งเดิมของพวกเขาเหมือนกัน ... " - เท็จอย่างแน่นอน หากคุณสร้างวัตถุห่อหุ้มสองชิ้นที่มีค่าเท่ากันวัตถุเหล่านั้นจะไม่กลับมาจริงเมื่อเปรียบเทียบกับ==เพราะเป็นวัตถุที่แตกต่างกัน
Dawood ibn Kareem

6

วัตถุจำนวนเต็มเก็บแคชระหว่าง -128 ถึง 127 จาก 256 จำนวนเต็ม

คุณไม่ควรเปรียบเทียบอ้างอิงวัตถุที่มี==หรือ! = คุณควรใช้ เท่ากับ (.. )แทนหรือดีกว่า - ใช้ int ดั้งเดิมแทนที่จะเป็นจำนวนเต็ม

parseInt : แยกวิเคราะห์อาร์กิวเมนต์สตริงเป็นจำนวนเต็มฐานสิบที่ลงนามแล้ว อักขระในสตริงทั้งหมดต้องเป็นตัวเลขทศนิยมยกเว้นว่าอักขระตัวแรกอาจเป็น ASCII ลบเครื่องหมาย '-' ('\ u002D') เพื่อระบุค่าลบ ค่าจำนวนเต็มที่เป็นผลลัพธ์จะถูกส่งกลับเหมือนกับอาร์กิวเมนต์และ radix 10 ถูกกำหนดเป็นอาร์กิวเมนต์ไปยังเมธอด parseInt (java.lang.String, int)

valueOf ส่งคืนวัตถุจำนวนเต็มที่เก็บค่าที่แยกออกมาจากสตริงที่ระบุเมื่อวิเคราะห์ด้วยค่าฐานที่กำหนดโดยอาร์กิวเมนต์ที่สอง อาร์กิวเมนต์แรกถูกตีความว่าเป็นตัวแทนจำนวนเต็มที่ลงนามใน Radix ที่ระบุโดยอาร์กิวเมนต์ที่สองเช่นเดียวกับถ้าอาร์กิวเมนต์ได้รับการกำหนดให้กับวิธี parseInt (java.lang.String, int) ผลลัพธ์ที่ได้คือวัตถุจำนวนเต็มที่แสดงถึงค่าจำนวนเต็มที่ระบุโดยสตริง

เทียบเท่ากับ

new Integer(Integer.parseInt(s, radix))

radix - radix ที่จะใช้ในการตีความ s

ดังนั้นถ้าคุณเท่ากับInteger.valueOf()จำนวนเต็มภายใน

-128 ถึง 127 มันจะคืนค่าจริงในสภาพของคุณ

สำหรับ lesser than-128 และgreater than127 มันให้false


6

เพื่อเสริมคำตอบที่ได้รับให้จดบันทึกสิ่งต่อไปนี้ด้วย:

public class Test { 
    public static void main(String... args) { 
        Integer a = new Integer(129);
        Integer b = new Integer(129);
        System.out.println(a == b);
    }
}

รหัสนี้จะพิมพ์ด้วย: false

เนื่องจากผู้ใช้Jayได้อ้างสิทธิ์ในข้อคิดเห็นสำหรับคำตอบที่ยอมรับต้องใช้ความระมัดระวังเมื่อใช้โอเปอเรเตอร์==กับวัตถุที่นี่คุณกำลังตรวจสอบว่าการอ้างอิงทั้งสองเหมือนกันซึ่งไม่ได้เนื่องจากพวกมันเป็น objets ที่แตกต่างกัน ค่าเดียวกัน ในการเปรียบเทียบวัตถุคุณควรใช้equals วิธีการแทน:

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

สิ่งนี้จะพิมพ์: true

คุณอาจถามแต่ทำไมพิมพ์บรรทัดแรกtrue? . ตรวจสอบซอร์สโค้ดของInteger.valueOfเมธอดคุณสามารถดูสิ่งต่อไปนี้:

public static Integer valueOf(String s) throws NumberFormatException {
    return Integer.valueOf(parseInt(s, 10));
}

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

หากพารามิเตอร์เป็นจำนวนเต็มระหว่างIntegerCache.low(ค่าเริ่มต้นถึง -128) และIntegerCache.high(คำนวณที่รันไทม์ที่มีค่าต่ำสุด 127) ดังนั้นวัตถุที่ถูกจัดสรรล่วงหน้า (แคช) จะถูกส่งคืน ดังนั้นเมื่อคุณใช้ 127 เป็นพารามิเตอร์คุณจะได้รับการอ้างอิงสองครั้งไปยังวัตถุแคชเดียวกันและรับtrueการเปรียบเทียบการอ้างอิง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.