ทำไมการเปรียบเทียบค่าสตริงของโอเปอเรเตอร์ == จึงไม่ทำให้เป็น Java


50

โปรแกรมเมอร์ Java ทุกคนรู้ดีว่าคุณต้องใช้ String.equals () เพื่อเปรียบเทียบสตริงแทนที่จะเป็น == เพราะ == ตรวจสอบความเท่าเทียมกันอ้างอิง

เมื่อฉันจัดการกับสตริงเวลาส่วนใหญ่ที่ฉันกำลังตรวจสอบความเท่าเทียมกันของค่ามากกว่าความเท่าเทียมกันอ้างอิง ดูเหมือนว่าสำหรับฉันแล้วมันจะง่ายกว่านี้หากภาษาอนุญาตให้เปรียบเทียบค่าสตริงโดยใช้ ==

จากการเปรียบเทียบตัวดำเนินการ == ของ C # จะตรวจสอบความเท่าเทียมกันของค่าสำหรับสตริง s และถ้าคุณต้องการตรวจสอบความเท่าเทียมกันของการอ้างอิงคุณสามารถใช้ String.ReferenceEquals

อีกจุดสำคัญคือสตริงไม่เปลี่ยนรูปดังนั้นจึงไม่มีอันตรายใด ๆ ที่จะทำโดยอนุญาตให้ใช้คุณลักษณะนี้

มีเหตุผลใดเป็นพิเศษหรือไม่ที่สิ่งนี้ไม่ได้นำไปใช้ใน Java?


12
คุณอาจต้องการดู Scala ซึ่ง==มีความเสมอภาคของวัตถุและeqเป็นความเท่าเทียมในการอ้างอิง ( ofps.oreilly.com/titles/9780596155957/ ...... )
Giorgio

เช่นเดียวกับโน้ตและนี่อาจช่วยคุณไม่ได้ แต่เท่าที่ฉันจำได้คุณสามารถเปรียบเทียบตัวอักษรสตริงกับ '=='
Kgrover

10
@Kgrover: คุณทำได้ แต่เป็นเพียงผลพลอยได้ที่สะดวกในการอ้างอิงความเท่าเทียมกันและวิธีการที่ Java ปรับสตริงตัวอักษรให้ตรงกับการอ้างอิงไปยังวัตถุเดียวกันอย่างจริงจัง มันทำงานได้ แต่ด้วยเหตุผลที่ผิด
tdammers

1
@aviv ตัว==ดำเนินการจะแม็พกับEqualsหากตัว==ดำเนินการถูกนำมาใช้ในลักษณะนั้น ลักษณะการทำงานเริ่มต้นสำหรับ==เป็นเช่นเดียวกับReferenceEquals(ที่จริงReferenceEqualsถูกกำหนดให้เป็นวัตถุของรุ่น==)
แพทริค Huizinga

3
มันเป็นผลมาจากการตัดสินใจออกแบบที่เหมาะสมในหลาย ๆ สถานการณ์ แต่เนื่องจากคุณดูเหมือนจะรู้และถามคำถามนี้ฉันรู้สึกว่าถูกบังคับให้ตอบคำถามของคุณ: ทำไมต้องมีกรณีการใช้งานสำหรับการเปรียบเทียบการอ้างอิงสตริง
Jacob Raihle

คำตอบ:


90

ฉันคิดว่ามันเป็นเพียงความมั่นคงหรือ "หลักการของความประหลาดใจอย่างน้อย" String เป็นวัตถุดังนั้นจึงน่าแปลกใจหากได้รับการปฏิบัติแตกต่างจากวัตถุอื่น ๆ

ในช่วงเวลาที่ Java ออกมา (~ 1995) เพียงแค่มีอะไรบางอย่างที่เหมือนกันStringคือความหรูหราโดยรวมสำหรับโปรแกรมเมอร์ส่วนใหญ่ที่คุ้นเคยกับการแสดงสตริงเป็นอาร์เรย์ที่สิ้นสุดด้วยค่า null Stringตอนนี้พฤติกรรมของมันคืออะไรแล้วมันก็ดีแล้ว การเปลี่ยนแปลงพฤติกรรมอย่างละเอียดในภายหลังอาจมีผลที่น่าประหลาดใจและไม่พึงประสงค์ในโปรแกรมการทำงาน

ตามบันทึกข้างคุณสามารถใช้String.intern()เพื่อให้ได้เป็นที่ยอมรับ (ฝึกงาน) ==เป็นตัวแทนของสตริงหลังจากที่รถอาจจะทำด้วย การฝึกงานจะใช้เวลาพอสมควร แต่หลังจากนั้นการเปรียบเทียบจะเร็วมาก

นอกจากนี้ไม่เหมือนกับคำตอบบางข้อที่แนะนำไม่เกี่ยวกับการรองรับการใช้งานเกินกำลัง ตัว+ดำเนินการ (การต่อข้อมูล) ทำงานบนStrings แม้ว่า Java ไม่รองรับการใช้งานเกินกำลัง StringBuilder.append()ก็จัดการเพียงแค่เป็นกรณีพิเศษในคอมไพเลอร์ที่จะแก้ไขปัญหา ในทำนองเดียวกัน==อาจได้รับการจัดการเป็นกรณีพิเศษ

แล้วทำไมต้องประหลาดใจกับกรณีพิเศษ+แต่ไม่ใช่ด้วย==? เพราะ+มันไม่ได้รวบรวมเมื่อนำไปใช้กับStringวัตถุที่ไม่ใช่เพื่อให้เห็นได้อย่างรวดเร็ว พฤติกรรมที่แตกต่างกันของ==จะชัดเจนน้อยลงและน่าประหลาดใจมากขึ้นเมื่อมันกระทบคุณ


8
กรณีพิเศษเพิ่มความประหลาดใจ
Blrfl

17
เงื่อนไขเป็นสิ่งที่หรูหราในปี 1995? จริงๆ?? ดูประวัติภาษาคอมพิวเตอร์ จำนวนภาษาที่มีสตริงบางประเภทในเวลานั้นจะมีจำนวนมากกว่าภาษาที่ไม่มี มีกี่ภาษานอกเหนือจาก C และผู้สืบทอดที่ใช้อาร์เรย์ที่ถูกยกเลิก null
WarrenT

14
@WarrenT: แน่นอนว่าบางภาษา (ถ้าไม่ใช่มากที่สุด) มีสตริงบางประเภท แต่สตริงที่เก็บรวบรวมขยะมีความสามารถ Unicode เป็นเรื่องแปลกในปี 1995 ฉันคิดว่า ตัวอย่างเช่น Python แนะนำสายอักขระ Unicode ที่มีรุ่น 2.0 ปี 2000 การเลือกความไม่เปลี่ยนรูปก็เป็นตัวเลือกที่ขัดแย้งกันในเวลานั้น
Joonas Pulakka

3
@JoonasPulakka งั้นคุณควรแก้ไขคำตอบของคุณเพื่อบอกว่า เนื่องจากคำว่า "ความหรูหราโดยรวม" ของคำตอบของคุณค่อนข้างผิด
svick

1
การฝึกงานระหว่างประเทศมีค่าใช้จ่าย: คุณจะได้รับสายอักขระที่จะไม่ถูกจัดสรรคืน (ก็ไม่ได้เว้นแต่ว่าคุณจะใช้เครื่องมือฝึกหัดที่คุณสามารถโยนทิ้งได้)
Donal Fellows

32

James Goslingผู้สร้าง Java อธิบายด้วยวิธีนี้ในเดือนกรกฎาคม 2000 :

ฉันออกจากการใช้งานเกินกำลังเป็นทางเลือกที่ค่อนข้างเป็นส่วนตัวเพราะฉันเห็นคนจำนวนมากเกินไปใช้มันใน C ++ ฉันใช้เวลามากในช่วงห้าถึงหกปีที่ผ่านมาสำรวจผู้คนเกี่ยวกับการบรรทุกเกินพิกัดและมันน่าสนใจมากเพราะคุณทำให้ชุมชนแตกออกเป็นสามส่วน: อาจประมาณ 20 ถึง 30 เปอร์เซ็นต์ของประชากรคิดว่า วางไข่ของปีศาจ; มีบางคนทำอะไรกับโอเปอเรเตอร์การบรรทุกเกินพิกัดซึ่งทำเครื่องหมายไว้จริง ๆ เพราะพวกเขาใช้เช่น + สำหรับการแทรกรายการและทำให้ชีวิตมีความสับสนจริงๆ ปัญหาดังกล่าวเกิดขึ้นจากข้อเท็จจริงที่ว่ามีผู้ให้บริการเพียงครึ่งโหลที่คุณสามารถใช้งานเกินพิกัดได้ แต่ยังมีผู้ให้บริการหลายพันหรือหลายล้านคนที่ผู้คนต้องการกำหนด - ดังนั้นคุณต้องเลือก


50
อ๊ะใช่เครื่องมือ "เก่า ๆ " ทื่อเครื่องมือที่แหลม ๆ เพื่อที่จะไม่ให้คนอื่นทำร้ายตัวเอง "
Blrfl

22
@Blrfl: หากเครื่องมือสร้างปัญหามากกว่าที่จะแก้มันไม่ใช่เครื่องมือที่ดี แน่นอนว่าการตัดสินใจว่านี่เป็นกรณีที่มีการบรรทุกเกินพิกัดหรือไม่นั้นอาจกลายเป็นการอภิปรายที่ยาวมาก
Giorgio

15
-1 นี่ไม่ได้ตอบคำถามเลย Java ไม่ได้มากไปประกอบการ ==ผู้ประกอบการมากเกินไปสำหรับวัตถุและวิทยาการ +ผู้ประกอบการมากเกินไปสำหรับbyte, short, int, long, float, double, Stringและอาจจะคู่ของคนอื่น ๆ ฉันลืม มันจะมีความเป็นไปได้อย่างสมบูรณ์แบบเกินพิกัด==สำหรับการStringได้เป็นอย่างดี
Jörg W Mittag

10
@ Jorg - ไม่ไม่ ตัวดำเนินการมากเกินไปเป็นไปไม่ได้ที่จะกำหนดในระดับผู้ใช้ มีบางกรณีพิเศษในคอมไพเลอร์ แต่ไม่ค่อยมีคุณสมบัติ
AZ01

9
@ Blrfl: ฉันไม่รังเกียจ oafs ทำร้ายตัวเอง เมื่อพวกเขาบังเอิญสบตาฉันออกมาฉันก็รู้สึกรำคาญ
Jonas

9

ความมั่นคงภายในภาษา การมีโอเปอเรเตอร์ที่ทำหน้าที่แตกต่างกันอาจทำให้โปรแกรมเมอร์ประหลาดใจ Java ไม่อนุญาตให้ผู้ใช้โอเวอร์โหลดโอเปอเรเตอร์ - ดังนั้นความเท่าเทียมในการอ้างอิงจึงเป็นความหมายที่สมเหตุสมผลเพียงอย่างเดียวสำหรับ==ระหว่างวัตถุ

ภายใน Java:

  • ระหว่างประเภท==ตัวเลขเปรียบเทียบความเท่าเทียมกันของตัวเลข
  • ระหว่างบูลีนประเภท==เปรียบเทียบความเท่าเทียมกันบูลีน
  • ระหว่างวัตถุ==เปรียบเทียบข้อมูลอ้างอิง
    • ใช้.equals(Object o)เพื่อเปรียบเทียบค่า

แค่นั้นแหละ. กฎง่าย ๆ และง่าย ๆ ในการระบุสิ่งที่คุณต้องการ นี้จะครอบคลุมทั้งหมดในส่วน 15.21 ของ JLS มันประกอบด้วยสามส่วนย่อยที่ง่ายต่อการเข้าใจใช้และเหตุผลเกี่ยวกับ

เมื่อคุณอนุญาตการใช้งานมากเกินไป==พฤติกรรมที่แน่นอนไม่ใช่สิ่งที่คุณสามารถมองหา JLS และวางนิ้วของคุณบนรายการที่เฉพาะเจาะจงและพูดว่า "นั่นเป็นวิธีการทำงาน" รหัสอาจกลายเป็นเรื่องยากที่จะให้เหตุผลเกี่ยวกับ พฤติกรรมที่แน่นอนของ==อาจเป็นที่น่าแปลกใจสำหรับผู้ใช้ ทุกครั้งที่คุณเห็นคุณต้องย้อนกลับไปและตรวจสอบเพื่อดูความหมายของมัน

เนื่องจาก Java ไม่อนุญาตให้มีการใช้งานตัวดำเนินการมากเกินไปเราจึงจำเป็นต้องมีวิธีการทดสอบความเท่าเทียมกันของค่าที่คุณสามารถแทนที่คำจำกัดความพื้นฐานของ ดังนั้นจึงได้รับคำสั่งจากตัวเลือกการออกแบบเหล่านี้ ==ใน Java ทดสอบตัวเลขสำหรับประเภทตัวเลขความเท่าเทียมกันแบบบูลสำหรับประเภทบูลีนและความเท่าเทียมกันอ้างอิงสำหรับทุกสิ่งอื่น (ซึ่งสามารถแทนที่ที่.equals(Object o)จะทำสิ่งที่พวกเขาต้องการสำหรับความเท่าเทียมกันค่า)

นี่ไม่ใช่ปัญหาของ "จะมีกรณีการใช้งานสำหรับผลลัพธ์เฉพาะของการตัดสินใจออกแบบนี้" แต่ "นี่คือการตัดสินใจออกแบบเพื่ออำนวยความสะดวกในสิ่งอื่น ๆ เหล่านี้นี่คือผลลัพธ์ของมัน"

String interningเป็นหนึ่งในตัวอย่างนี้ ตามJLS 3.10.5ตัวอักษรสตริงทั้งหมดจะถูกฝึกงาน สตริงอื่น ๆ จะถูก interned หากมีใครเรียก.intern()พวกเขา นั่น"foo" == "foo"เป็นความจริงเป็นผลมาจากการตัดสินใจออกแบบเพื่อลดขนาดหน่วยความจำที่ใช้โดยตัวอักษร String ยิ่งไปกว่านั้น String Interning เป็นสิ่งที่อยู่ในระดับ JVM ที่มีการเปิดเผยต่อผู้ใช้เล็กน้อย แต่ในกรณีส่วนใหญ่ที่ครอบงำไม่ควรเป็นสิ่งที่เกี่ยวข้องกับโปรแกรมเมอร์ (และใช้กรณีสำหรับโปรแกรมเมอร์ไม่ใช่ สิ่งที่สูงในรายการสำหรับนักออกแบบเมื่อพิจารณาคุณสมบัตินี้)

ผู้คนจะชี้ให้เห็นว่า+และ+=มีมากเกินไปสำหรับ String อย่างไรก็ตามนั่นไม่ได้อยู่ที่นี่หรือที่นั่น มันยังคงเป็นกรณีที่ถ้า==มีความหมายความเท่าเทียมกันค่าสำหรับ String (และสตริงเท่านั้น) หนึ่งจะต้องมีวิธีการที่แตกต่างกัน (ที่มีอยู่ใน String เท่านั้น) สำหรับความเท่าเทียมกันอ้างอิง นอกจากนี้วิธีนี้จะไม่ซับซ้อนโดยไม่จำเป็นซึ่งใช้ Object และคาดว่า==จะมีวิธีการหนึ่งและ.equals()จะต้องมีวิธีอื่นที่ผู้ใช้ต้องการในกรณีพิเศษสำหรับเมธอดเหล่านั้นทั้งหมดสำหรับ String

สัญญาที่สอดคล้องกันสำหรับ==บนวัตถุก็คือมันเป็นความเท่าเทียมกันอ้างอิงเท่านั้นและที่.equals(Object o)มีอยู่สำหรับวัตถุทั้งหมดที่ควรทดสอบความเท่าเทียมกันของค่า ซับซ้อนนี้ซับซ้อนมากเกินไปสิ่งต่าง ๆ


ขอบคุณที่สละเวลาตอบ นี่จะเป็นคำตอบที่ดีสำหรับคำถามอื่น ๆ ที่ฉันเชื่อมโยง น่าเสียดายที่มันไม่เหมาะกับคำถามนี้ ฉันจะอัปเดต OP พร้อมคำอธิบายตามความคิดเห็น ฉันกำลังมองหากรณีการใช้งานมากขึ้นซึ่งผู้ใช้ภาษาต้องการมีข้อผิดพลาดเชิงลบเมื่อเปรียบเทียบสตริง ภาษาให้คุณสมบัตินี้อย่างสม่ำเสมอตอนนี้ฉันอยากให้เราก้าวไปอีกขั้น อาจจะคิดถึงสิ่งนี้จากนักออกแบบภาษาใหม่มันจำเป็นหรือไม่ (น่าเสียดายที่ไม่มี lang-design.SE)
Anonsage

3
@ ให้ความเห็นว่าไม่ใช่เชิงลบที่ผิด มันไม่ใช่วัตถุเดียวกัน นั่นคือทั้งหมดที่มันพูด ฉันต้องชี้ให้เห็นว่าใน Java 8 new String("foo") == new String("foo")อาจจะเป็นจริง (ดูString Deduplication )

1
สำหรับการออกแบบภาษาCS.SEโฆษณาว่าอาจอยู่ในหัวข้อนั้น

โอ้ขอบคุณ! ฉันจะโพสต์คำถามการออกแบบภาษาในอนาคตที่นั่น :) และใช่โชคไม่ดีที่ 'เท็จลบ' ไม่ใช่วิธีที่แม่นยำที่สุดในการอธิบายคำถามของฉันและสิ่งที่ฉันกำลังมองหา .. ฉันต้องเขียนคำเพิ่มเติมเพื่อที่ผู้คนจะไม่ต้องเดาว่าฉันเป็นใคร พยายามพูด
Anonsage

2
"ความสอดคล้องในภาษา" ยังช่วยในเรื่องทั่วไปด้วย
เบรนแดน

2

Java ไม่รองรับการบรรทุกเกินพิกัดซึ่งหมายถึง==เฉพาะกับประเภทหรือการอ้างอิงดั้งเดิม อะไรก็ได้ที่ต้องมีการร้องขอวิธีการ เหตุใดนักออกแบบจึงเป็นคำถามที่พวกเขาสามารถตอบได้เท่านั้น ถ้าฉันต้องเดาอาจเป็นเพราะผู้ให้บริการมากไปทำให้เกิดความซับซ้อนที่พวกเขาไม่ได้สนใจที่จะเพิ่ม

ฉันไม่มีความเชี่ยวชาญใน C # แต่นักออกแบบของภาษานั้นได้ตั้งค่าดังกล่าวเพื่อให้ทุกคนดั้งเดิมstructและทุกคนstructเป็นวัตถุ เนื่องจาก C # อนุญาตให้ผู้ปฏิบัติงานโอเวอร์โหลดการจัดเรียงนั้นทำให้มันง่ายมากสำหรับชั้นเรียนใด ๆ ไม่เพียง แต่Stringจะทำให้ตัวเองทำงานในลักษณะ "คาดหวัง" กับผู้ประกอบการใด ๆ C ++ อนุญาตในสิ่งเดียวกัน


1
"Java ไม่รองรับการบรรทุกเกินพิกัดของผู้ปฏิบัติงานซึ่งหมายถึง == ใช้กับประเภทหรือการอ้างอิงดั้งเดิมเท่านั้นสิ่งอื่น ๆ ที่ต้องการการเรียกใช้วิธีการ": เราสามารถเพิ่มได้หาก==หมายถึงความเท่าเทียมกันของสตริง
Giorgio

@Giorgio: แน่นอน ดูความคิดเห็นของฉันเกี่ยวกับคำตอบของ Gilad Naaman
Blrfl

แม้ว่าจะสามารถแก้ไขได้โดยวิธีการคงที่ที่เปรียบเทียบการอ้างอิงของวัตถุสองรายการ (หรือผู้ดำเนินการ) เช่นใน C # ตัวอย่างเช่น
Gilad Naaman

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

1
@Blrfl: ไม่จริง ก็คือจะมีวิธีการที่กำหนดไว้เพื่อเปรียบเทียบอ้างอิง ( ClassName.ReferenceEquals(a,b)) และเริ่มต้น==ดำเนินการและวิธีการทั้งชี้ไปที่Equals ReferenceEquals
Gilad Naaman

2

สิ่งนี้ทำให้แตกต่างในภาษาอื่น ๆ

ใน Object Pascal (Delphi / Free Pascal) และ C # ตัวดำเนินการความเสมอภาคถูกกำหนดเพื่อเปรียบเทียบค่าไม่ใช่การอ้างอิงเมื่อทำงานกับสตริง

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

ดังนั้นมันเป็นการตัดสินใจออกแบบภาษาสำหรับ Java เมื่อพวกเขาออกแบบภาษาพวกเขาใช้วิธี C ++ (เช่น Std :: String) ดังนั้นสตริงจึงเป็นวัตถุซึ่งเป็น IMHO ที่แฮ็คเพื่อชดเชย C ที่ไม่มีประเภทสตริงจริงแทนที่จะสร้างสตริงเป็นแบบดั้งเดิม (ซึ่งเป็น)

ดังนั้นด้วยเหตุผลว่าทำไมฉันสามารถคาดเดาได้ว่าพวกเขาทำให้มันง่ายที่ด้านข้างของพวกเขาและไม่เข้ารหัสผู้ประกอบการทำให้ข้อยกเว้นในคอมไพเลอร์กับสตริง


คำถามนี้ถามคำถามนี้อย่างไร
ริ้น

ดูประโยคสุดท้าย (ซึ่งฉันแยกในย่อหน้าที่ถูกต้องในการแก้ไข)
Fabricio Araujo

1
IMHO Stringควรเป็นประเภทดั้งเดิมใน Java ซึ่งแตกต่างจากชนิดอื่น ๆ คอมไพเลอร์ต้องการทราบเกี่ยวกับString; นอกจากนี้การดำเนินการกับมันจะเป็นเรื่องธรรมดามากพอสำหรับการใช้งานหลายประเภทพวกเขาอาจก่อให้เกิดปัญหาคอขวดในการทำงาน โดยทั่วไปstring[ตัวพิมพ์เล็ก] จะมีวัตถุที่ถูกจัดสรรบนฮีปเพื่อเก็บเนื้อหา แต่ไม่มีการอ้างอิง "ปกติ" ไปยังวัตถุนั้นที่ใดก็ได้ มันอาจจะเป็นทางอ้อมเดียวChar[]หรือByte[]แทนที่จะต้องเป็นChar[]ทางอ้อมผ่านวัตถุอื่น
supercat

1

ใน Java ไม่มีโอเปอเรเตอร์ที่บรรทุกเกินพิกัดใด ๆ และนั่นเป็นสาเหตุที่โอเปอเรเตอร์การเปรียบเทียบถูกโอเวอร์โหลดสำหรับประเภทดั้งเดิมเท่านั้น

คลาส 'String' ไม่ใช่แบบดั้งเดิมดังนั้นจึงไม่มีการโหลดมากเกินไปสำหรับ '==' และใช้ค่าเริ่มต้นในการเปรียบเทียบที่อยู่ของวัตถุในหน่วยความจำของคอมพิวเตอร์

ผมไม่แน่ใจ แต่ผมคิดว่าใน Java 7 หรือ 8 oracle ทำข้อยกเว้นในคอมไพเลอร์ที่จะรับรู้str1 == str2เป็นstr1.equals(str2)


"ฉันไม่แน่ใจ แต่ฉันคิดว่าใน Java 7 หรือ 8 oracle ได้สร้างข้อยกเว้นในคอมไพเลอร์ให้รู้จัก str1 == str2 เป็น str1.equals (str2)": ฉันจะไม่แปลกใจ: Oracle ดูเหมือนจะกังวลน้อยกว่า ด้วยความเรียบง่ายกว่าดวงอาทิตย์
Giorgio

2
ถ้าเป็นจริงนั่นเป็นแฮ็คที่น่าเกลียดมากเพราะมันหมายความว่าขณะนี้มีคลาสหนึ่งภาษาที่ปฏิบัติแตกต่างจากผู้อื่นทั้งหมดและแบ่งรหัสที่เปรียบเทียบการอ้างอิง : - @
Blrfl

1
@WillihamTotland: พิจารณากรณีตรงข้าม ในปัจจุบันถ้าฉันสร้างสองสายs1และs2และให้เนื้อหาเดียวกันพวกเขาจะผ่านความเท่าเทียมกัน ( s1.equals(s2)) การเปรียบเทียบ แต่ไม่ใช่การเปรียบเทียบที่อ้างอิงเดียวกัน ( ==) เพราะพวกเขาเป็นสองวัตถุที่แตกต่างกัน การเปลี่ยนซีแมนทิกส์ของ==เป็นหมายถึงความเท่าเทียมกันจะทำให้เกิดs1 == s2การประเมินtrueว่าจะใช้ในการประเมินที่falseใด
Blrfl

2
@Brlfl: ในขณะที่เป็นจริงดูเหมือนว่าเป็นสิ่งที่ไม่ดีเป็นพิเศษที่จะพึ่งพาในสถานที่แรกเป็นสตริงเป็นวัตถุที่ไม่เปลี่ยนรูปและไม่สามารถแก้ไขได้
Williham Totland

2
@Giorgio: "Java 7 หรือ 8 oracle สร้างข้อยกเว้นในคอมไพเลอร์เพื่อรับรู้ str1 == str2 เป็น str1.equals (str2)" ไม่ , Java Language Specification พูดว่า: Reference Equality Operators : "ถ้าตัวถูกดำเนินการของตัวดำเนินการเท่าเทียมกันคือ ทั้งชนิดอ้างอิงหรือชนิด null จากนั้นการดำเนินการคือความเสมอภาคของวัตถุ " นั่นคือคนทั้งหมด ฉันไม่พบอะไรใหม่เกี่ยวกับเรื่องนี้ในJava 8 Early draftเช่นกัน
David Tonhofer

0

Java ดูเหมือนจะได้รับการออกแบบมาเพื่อสนับสนุนกฎพื้นฐานที่==ผู้ประกอบการควรถูกกฎหมายเมื่อใดก็ตามที่ตัวถูกดำเนินการหนึ่งสามารถถูกแปลงเป็นประเภทของอื่น ๆ และควรเปรียบเทียบผลของการแปลงดังกล่าวกับตัวถูกดำเนินการไม่แปลง

กฎนี้ไม่ค่อยมีลักษณะเฉพาะสำหรับ Java แต่มีผลกระทบระยะไกล (และ IMHO ที่ไม่เอื้ออำนวย) ในการออกแบบภาษาที่เกี่ยวข้องกับประเภทอื่น ๆ มันจะเป็นการดีกว่าที่จะระบุพฤติกรรมของ==ชุดตัวถูกดำเนินการโดยเฉพาะและห้ามการรวมประเภท X และ Y ที่x1==y1และx2==y1ไม่บอกเป็นนัยx1==x2แต่ภาษาแทบจะไม่ทำเช่นนั้น [ภายใต้ปรัชญานั้นdouble1 == long1จะต้องระบุว่าdouble1ไม่ได้เป็นตัวแทนที่แน่นอนของlong1หรือมิฉะนั้นปฏิเสธที่จะรวบรวม;int1==Integer1ควรเป็นสิ่งต้องห้าม แต่ควรมีวิธีทดสอบแบบไม่ใช้วิธีโยนที่สะดวกและมีประสิทธิภาพว่าวัตถุนั้นเป็นจำนวนเต็มชนิดบรรจุกล่องที่มีค่าเฉพาะหรือไม่ (เปรียบเทียบกับสิ่งที่ไม่ใช่จำนวนเต็มแบบกล่องควรส่งคืนfalse)]

ในแง่ของการใช้==โอเปอเรเตอร์กับสตริงถ้า Java มีการห้ามเปรียบเทียบโดยตรงระหว่างตัวถูกดำเนินการประเภทStringและObjectมันสามารถหลีกเลี่ยงความประหลาดใจในพฤติกรรมที่==ดีได้ การมีสองการอ้างอิงสตริงที่เก็บไว้ในObjectลักษณะการทำงานที่แตกต่างจากการอ้างอิงที่เก็บไว้ในประเภทStringจะได้รับความประหลาดใจน้อยกว่าพฤติกรรมอย่างใดอย่างหนึ่งเหล่านั้นแตกต่างจากการเปรียบเทียบแบบผสมทางกฎหมาย หากString1==Object1ถูกกฎหมายนั่นหมายความว่าวิธีเดียวที่จะทำให้พฤติกรรมของString1==String2และObject1==Object2การจับคู่String1==Object1เป็นไปได้สำหรับพวกเขาที่จะจับคู่กัน


ฉันจะต้องหายไปบางสิ่งบางอย่าง แต่ IMHO ==บนวัตถุควรเพียงแค่เรียก (ปลอดภัยเป็นโมฆะ) เท่ากับและอย่างอื่น (เช่น===หรือSystem.identityEqual) ควรใช้สำหรับการเปรียบเทียบตัวตน การผสมดั้งเดิมและวัตถุจะถูกห้ามในขั้นต้น (ไม่มีการ autoboxing ก่อน 1.5) และจากนั้นจะพบกฎง่ายๆบางอย่าง (เช่นกล่องนิรภัยแบบ null-safe จากนั้นจึงส่งและเปรียบเทียบ)
maaartinus

@ maaartinus: การออกแบบภาษาที่ดีควรใช้โอเปอเรเตอร์ความเสมอภาคแยกต่างหากสำหรับค่าและความเท่าเทียมกันอ้างอิง ในขณะที่ฉันเห็นด้วยว่าในทางความคิดมันเป็นไปได้ที่จะมีตัวint==Integerดำเนินการคืนค่าfalseถ้าIntegerค่านั้นเป็นโมฆะและเปรียบเทียบค่าอื่น ๆ วิธีการนั้นจะไม่เหมือนกับพฤติกรรมของ==ในสถานการณ์อื่น ๆ ทั้งหมด เปรียบเทียบพวกเขา ส่วนตัวผมสงสัยว่าถ้าอัตโนมัติ unboxing ถูกวางในสถานที่ในความพยายามที่จะทำให้ผู้int==Integerที่จะมีพฤติกรรมที่ไม่ไร้สาระ ...
SuperCat

... เนื่องจากการ autoboxing intและการเปรียบเทียบการอ้างอิงนั้นอาจเป็นเรื่องโง่เง่า มิฉะนั้นฉันไม่เห็นเหตุผลที่จะอนุญาตให้มีการแปลงโดยนัยซึ่งอาจล้มเหลวด้วย NPE
supercat

ฉันคิดว่าความคิดของฉันสอดคล้อง เพียงแค่ให้มันใจว่าในโลกที่ดีกว่ามีอะไรจะทำอย่างไรกับ== identityEquals+++ "ตัวดำเนินการความเสมอภาคแบบแยกต่างหากสำหรับค่าและความเท่าเทียมกันอ้างอิง" - แต่ตัวไหนกัน ฉันจะพิจารณาทั้งแบบดั้งเดิม==และequalsทำการเปรียบเทียบค่าในแง่ที่equalsดูค่าของการอ้างอิง +++ เมื่อ==หมายถึงequalsแล้วint==Integerควรจะทำอย่างไร autoboxing และเปรียบเทียบอ้างอิงใช้เท่ากับ null ปลอดภัย +++ ฉันกลัวความคิดของฉันไม่ใช่ของฉันจริงๆ แต่เป็นสิ่งที่ Kotlin ทำ
maaartinus

@maaartinus: หาก==ไม่เคยทดสอบความเท่าเทียมกันของการอ้างอิงมันก็จะสามารถทำการทดสอบที่มีค่าเป็นศูนย์ได้อย่างปลอดภัย ความจริงที่ว่ามันไม่เท่าเทียมกันอ้างอิงทดสอบ แต่ข้อ จำกัด อย่างรุนแรงวิธีที่จะสามารถจัดการกับการเปรียบเทียบอ้างอิงผสม / ค่าโดยไม่ต้องความไม่สอดคล้องกัน โปรดสังเกตว่า Java ได้รับการแก้ไขในความคิดที่ว่าตัวดำเนินการส่งเสริมตัวถูกดำเนินการทั้งสองประเภทเดียวกันแทนที่จะให้พฤติกรรมพิเศษตามการรวมกันของประเภทที่เกี่ยวข้อง ตัวอย่างเช่น16777217==16777216.0fส่งคืนtrueเนื่องจากมันทำการแปลง lossy ของตัวถูกดำเนินการแรกเป็นfloatขณะที่ ...
supercat

0

โดยทั่วไปมีเหตุผลที่ดีมากที่ต้องการทดสอบว่าการอ้างอิงวัตถุสองรายการชี้ไปที่วัตถุเดียวกันหรือไม่ ฉันมีเวลามากมายที่ฉันเขียน

Address oldAddress;
Address newAddress;
... populate values ...
if (oldAddress==newAddress)
... etc ...

ฉันอาจหรือไม่อาจมีฟังก์ชั่นเท่ากับในกรณีเช่นนี้ ถ้าฉันทำเช่นนั้นฟังก์ชั่นเท่ากับอาจเปรียบเทียบเนื้อหาทั้งหมดของวัตถุทั้งสอง บ่อยครั้งที่มันเพิ่งเปรียบเทียบตัวระบุบางอย่าง "A และ B อ้างอิงถึงวัตถุเดียวกัน" และ "A และ B เป็นวัตถุสองชิ้นที่แตกต่างกันที่มีเนื้อหาเดียวกัน" แน่นอนว่าเป็นแนวคิดสองแนวคิดที่แตกต่างกันมาก

อาจเป็นเรื่องจริงสำหรับวัตถุที่ไม่เปลี่ยนรูปเช่น Strings นี่เป็นปัญหาน้อยกว่า ด้วยวัตถุที่ไม่เปลี่ยนรูปเรามักจะคิดถึงวัตถุและคุณค่าที่เป็นสิ่งเดียวกัน เมื่อฉันพูดว่า "พวกเรา" ฉันหมายถึง "ฉัน" อย่างน้อย

Integer three=new Integer(3);
Integer triangle=new Integer(3);
if (three==triangle) ...

แน่นอนว่าผลตอบแทนที่เป็นเท็จ แต่ฉันเห็นคนที่คิดว่ามันควรจะเป็นจริง

แต่เมื่อคุณพูดว่า == เปรียบเทียบการอ้างอิงและไม่ใช่เนื้อหาสำหรับ Objects โดยทั่วไปการสร้างกรณีพิเศษสำหรับ Strings อาจทำให้เกิดความสับสน อย่างที่คนอื่นที่นี่พูดว่าจะทำอย่างไรถ้าคุณต้องการเปรียบเทียบที่จับของวัตถุ String สองอัน? มีฟังก์ชั่นพิเศษที่จะทำเฉพาะกับ Strings หรือไม่

และสิ่งที่เกี่ยวกับ ...

Object x=new String("foo");
Object y=new String("foo");
if (x==y) ...

นั่นเป็นเท็จเพราะพวกเขาเป็นวัตถุสองชิ้นที่แตกต่างกันหรือเป็นจริงเพราะพวกเขาเป็นสตริงที่มีเนื้อหาเท่ากัน?

ใช่ฉันเข้าใจว่าโปรแกรมเมอร์สับสนอย่างไร ฉันทำมันเองฉันหมายถึงเขียนถ้า myString == "foo" เมื่อฉันหมายถึงถ้า myString.equals ("foo") แต่ขาดการออกแบบความหมายของตัวดำเนินการ == สำหรับวัตถุทั้งหมดใหม่ฉันไม่เห็นวิธีการแก้ไข


โปรดทราบว่าภาษาสมัยใหม่อื่น ๆ ใน JVM เช่น Scala ใช้==เพื่อหมายถึง "สตริงที่เท่ากัน"
Andres F.

@AndresF (ยัก) ใน Java "<" หมายถึง "น้อยกว่า" ในขณะที่ใน XML มัน "เปิดแท็ก" ใน VB "=" สามารถหมายถึง "เท่ากับ" ในขณะที่ Java ใช้สำหรับการมอบหมายเท่านั้น เป็นต้นมันแทบจะไม่น่าแปลกใจที่ภาษาต่าง ๆ ใช้สัญลักษณ์ที่แตกต่างกันเพื่อหมายถึงสิ่งเดียวกันหรือสัญลักษณ์เดียวกันเพื่อหมายถึงสิ่งต่าง ๆ
Jay

มันไม่ใช่คำตอบของคุณ มันเป็นเพียงความคิดเห็น ฉันแค่ชี้ให้เห็นว่าภาษาที่ทันสมัยกว่า Java ใช้โอกาสในการออกแบบความหมาย==ใหม่เช่นเดียวกับที่คุณพูดถึงในตอนท้าย
Andres F.

@AndresF และคำตอบของฉันไม่ได้เป็นการขุดที่ความคิดเห็นของคุณเพียงแค่บอกว่าภาษาต่าง ๆ เข้าหาปัญหาเหล่านี้ในรูปแบบที่แตกต่างกัน :-) ฉันชอบวิธีที่ VB จัดการสิ่งนี้ ... หยุดชั่วคราวเพื่อขู่ฟ่อและเสียงโห่ร้องจากผู้เกลียดชัง VB ... "=" จะเปรียบเทียบค่าเสมอ (สำหรับประเภทที่กำหนดโดยระบบ) ไม่ว่าจะเป็นแบบดั้งเดิมหรือวัตถุ "Is" เปรียบเทียบหมายเลขอ้างอิงของวัตถุสองรายการ ดูเหมือนจะง่ายกว่าสำหรับฉัน
Jay

แน่ใจ แต่ Scala นั้นใกล้ชิดกับ Java มากกว่า Visual Basic ฉันชอบคิดว่านักออกแบบของสกาล่าตระหนักดีว่าการใช้งานจาวา==นั้นเกิดข้อผิดพลาดได้ง่าย
Andres F.

0

นี่เป็นคำถามที่ถูกต้องสำหรับการStringsและไม่เพียง แต่สำหรับสตริง แต่ยังไม่เปลี่ยนรูปวัตถุอื่น ๆ ที่เป็นตัวแทนของบางคน "ค่า" เช่นDouble, และแม้กระทั่งBigIntegerInetAddress

สำหรับการทำให้==ผู้ประกอบการใช้งานได้กับ Strings และคลาสค่าอื่น ๆ ฉันเห็นทางเลือกสามทาง:

  • ให้คอมไพเลอร์ทราบเกี่ยวกับคลาสค่าเหล่านี้ทั้งหมดและวิธีเปรียบเทียบเนื้อหา ถ้ามันเป็นแค่คลาสหนึ่งจากjava.langแพ็คเกจฉันจะพิจารณามัน แต่นั่นไม่ครอบคลุมถึงกรณีอย่าง InetAddress

  • อนุญาตให้ผู้ประกอบการมากไปดังนั้นคลาสกำหนด==พฤติกรรมการเปรียบเทียบ

  • ลบตัวสร้างพับลิกและมีวิธีการสแตติกที่ส่งคืนอินสแตนซ์จากกลุ่มโดยส่งคืนอินสแตนซ์เดียวกันเสมอสำหรับค่าเดียวกัน เพื่อหลีกเลี่ยงการรั่วไหลของหน่วยความจำคุณต้องมีสิ่งที่ชอบ SoftReferences ในพูลซึ่งไม่มีอยู่ใน Java 1.0 และตอนนี้เพื่อรักษาความเข้ากันได้ตัวString()สร้างไม่สามารถลบได้อีกต่อไป

สิ่งเดียวที่สามารถทำได้ในวันนี้คือการแนะนำผู้ให้บริการมากไปและโดยส่วนตัวแล้วฉันไม่อยากให้ Java ไปเส้นทางนั้น

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

มีเพียงด้านเดียวของการเปรียบเทียบของ Java ที่สร้างความรำคาญให้ฉันคือ: ผลของการชกมวยอัตโนมัติและ -unboxing มันซ่อนความแตกต่างระหว่างชนิดดั้งเดิมและชนิดหุ้มห่อ แต่เมื่อคุณเปรียบเทียบพวก==เขาพวกเขาจะแตกต่างกันมาก

    int i=123456;
    Integer j=123456;
    Integer k=123456;
    System.out.println(i==j);  // true or false? Do you know without reading the specs?
    System.out.println(j==k);  // true or false? Do you know without reading the specs?
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.