อินสแตนซ์อาจเท่ากับอินสแตนซ์อื่นที่เป็นประเภทเฉพาะเจาะจงมากกว่าหรือไม่


25

ฉันได้อ่านบทความนี้: วิธีการเขียนวิธีที่เท่าเทียมกันในชวา

โดยพื้นฐานแล้วจะมีวิธีแก้ปัญหาสำหรับวิธีเท่ากับ () ที่รองรับการสืบทอด:

Point2D twoD   = new Point2D(10, 20);
Point3D threeD = new Point3D(10, 20, 50);
twoD.equals(threeD); // true
threeD.equals(twoD); // true

แต่มันเป็นความคิดที่ดี? อินสแตนซ์ทั้งสองนี้ดูเหมือนจะเท่ากัน แต่อาจมีรหัสแฮชที่แตกต่างกันสองแบบ ไม่ผิดหรอกเหรอ?

ฉันเชื่อว่าสิ่งนี้จะสำเร็จได้ดีกว่าโดยการคัดเลือกตัวถูกดำเนินการแทน


1
ตัวอย่างที่มีจุดสีตามที่ระบุในลิงก์ทำให้ฉันเข้าใจได้ง่ายขึ้น ฉันจะพิจารณาว่าจุด 2D (x, y) สามารถมองเห็นเป็นจุด 3 มิติที่มีส่วนประกอบศูนย์ Z (x, y, 0) และฉันต้องการความเท่าเทียมกันที่จะส่งกลับเท็จในกรณีของคุณ ในความเป็นจริงในบทความ ColoredPoint ได้รับการกล่าวอย่างชัดเจนว่าแตกต่างจาก Point และกลับเท็จเสมอ
coredump

10
ไม่มีอะไรจะแย่ไปกว่าแบบฝึกหัดที่ทำลายหลักการทั่วไป ... มันต้องใช้เวลาหลายปีกว่าจะทำลายนิสัยแบบนั้น
corsiKa

3
@coredump การรักษาจุด 2 มิติว่ามีการzประสานงานเป็นศูนย์อาจเป็นข้อตกลงที่เป็นประโยชน์สำหรับบางแอปพลิเคชัน แต่มันเป็นแบบแผนโดยพลการ เครื่องบินในอวกาศที่มี 3 มิติขึ้นไปสามารถมีทิศทางที่กำหนดเอง ... มันทำให้ปัญหาที่น่าสนใจน่าสนใจ
Ben rudgers

2
มันเป็นมากกว่าผิดเล็กน้อย
Kevin Krumwiede

คำตอบ:


71

นี้ไม่ควรจะเท่าเทียมกันเพราะมันแบ่งกริยา ลองพิจารณาสองสำนวนนี้:

new Point3D(10, 20, 50).equals(new Point2D(10, 20)) // true
new Point2D(10, 20).equals(new Point3D(10, 20, 60)) // true

เนื่องจากความเสมอภาคเป็นสกรรมกริยานี่ควรหมายความว่าการแสดงออกต่อไปนี้ก็เป็นจริงเช่นกัน:

new Point3D(10, 20, 50).equals(new Point3D(10, 20, 60))

แต่แน่นอน - มันไม่ใช่

ดังนั้นความคิดของคุณในการคัดเลือกที่ถูกต้อง - คาดว่าใน Java การหล่อนั้นหมายถึงการคัดเลือกประเภทของการอ้างอิง สิ่งที่คุณต้องการจริงๆที่นี่คือวิธีการแปลงที่จะสร้างPoint2Dวัตถุใหม่จากPoint3Dวัตถุ สิ่งนี้จะทำให้การแสดงออกมีความหมายมากขึ้น:

twoD.equals(threeD.projectXY())

1
บทความอธิบายการใช้งานที่ทำลายความไวแสงและเสนอช่วงของการหลีกเลี่ยงงาน ในโดเมนที่เราอนุญาตให้ใช้คะแนน 2D เราได้ตัดสินใจแล้วว่ามิติที่สามไม่สำคัญ และเพื่อให้(10, 20, 50)เท่ากับ (10, 20, 60)เป็นเรื่องปกติ เราสนใจ10และ20เท่านั้น
Ben rudgers

1
ควรPoint2DมีprojectXYZ()วิธีในการPoint3Dแสดงถึงตัวของมันเองหรือไม่? ในคำอื่น ๆการใช้งานควรรู้จักกันหรือไม่
hjk

4
@hjk การกำจัดPoint2Dดูเหมือนง่ายขึ้นเนื่องจากการฉายจุด 2 มิติจำเป็นต้องกำหนดระนาบของพวกเขาในอวกาศ 3 มิติก่อน หากจุด 2D รู้ว่าเป็นระนาบแสดงว่าเป็นจุด 3 มิติอยู่แล้ว หากไม่เป็นเช่นนั้นก็ไม่สามารถฉายภาพได้ ฉันนึกถึงของแอ๊บบอตFlatland
Ben rudgers

@benrudgers คุณสามารถกำหนดPlane3Dวัตถุซึ่งจะกำหนดระนาบในพื้นที่ 3 มิติระนาบนั้นสามารถมีliftวิธี (2D-> 3D กำลังยกขึ้นไม่ใช่ฉาย) ซึ่งจะยอมรับ a Point2Dและตัวเลขสำหรับ "แกนที่สาม "- ระยะทางจากระนาบตามแนวระนาบปกติ เพื่อความสะดวกในการใช้งานคุณสามารถกำหนดระนาบทั่วไปเป็นค่าคงที่แบบคงที่ดังนั้นคุณสามารถทำสิ่งต่าง ๆ เช่นPlane3D.XY.lift(new Point2D(10, 20), 50).equals(new Point3D(10, 20, 50))
Idan Arye

@IdanArye ฉันแสดงความคิดเห็นเกี่ยวกับข้อเสนอแนะว่าคะแนน 2D ควรมีวิธีการฉายภาพ สำหรับเครื่องบินที่มีวิธีการยกผมคิดว่ามันต้องมีสองข้อโต้แย้งเพื่อให้เข้าใจ: เป็นจุด 2D และระนาบที่คาดว่าจะเปิดอยู่นั่นคือมันจำเป็นต้องมีการฉายภาพหากมันไม่ได้เป็นเจ้าของจุด ... และถ้ามันเป็นเจ้าของจุดทำไมไม่เพียงแค่เป็นเจ้าของจุด 3D และกำจัดด้วยชนิดข้อมูลที่เป็นปัญหาและกลิ่นของวิธีการ kludged? YMMV
Ben rudgers

10

ฉันเดินออกไปจากการอ่านบทความที่คิดเกี่ยวกับภูมิปัญญาของ Alan J. Perlis:

ตอนที่ 9 ดีกว่าที่จะให้ 100 ฟังก์ชันทำงานในโครงสร้างข้อมูลเดียวมากกว่า 10 ฟังก์ชันในโครงสร้างข้อมูล 10

ความจริงที่ว่าการได้รับ "ความเท่าเทียมกัน" นั้นเป็นปัญหาที่ทำให้Martin Orderskyนักประดิษฐ์ของสกาล่าตอนกลางคืนควรหยุดชั่วคราวว่าการเอาชนะequalsในต้นไม้มรดกนั้นเป็นความคิดที่ดีหรือไม่

เกิดอะไรขึ้นเมื่อเราโชคไม่ดีที่จะได้รับColoredPointรูปทรงเรขาคณิตของเราล้มเหลวเพราะเราใช้การสืบทอดเพื่อเพิ่มจำนวนชนิดข้อมูลแทนที่จะทำให้ดีขึ้น สิ่งนี้แม้จะต้องย้อนกลับไปและแก้ไขโหนดรูทของแผนผังการสืบทอดเพื่อให้equalsทำงานได้ ทำไมไม่เพียงเพิ่มzและcolorเพื่อPoint?

เหตุผลที่ดีนั้นจะPointและColoredPointดำเนินการในโดเมนที่แตกต่างกัน ... อย่างน้อยถ้าโดเมนเหล่านั้นไม่ได้ปะปนกัน equalsแต่ถ้าเป็นกรณีที่เราไม่จำเป็นต้องแทนที่ การเปรียบเทียบColoredPointและPointเพื่อความเท่าเทียมจะมีเหตุผลเฉพาะในโดเมนที่สามที่พวกเขาได้รับอนุญาตให้ปะปนกัน และในกรณีนั้นมันอาจจะดีกว่าถ้ามี "ความเสมอภาค" ที่ปรับให้เหมาะกับโดเมนที่สามนั้นแทนที่จะพยายามใช้ซีแมนทิกส์เท่าเทียมกันจากโดเมนหนึ่งหรือโดเมนอื่นหรือทั้งสองอย่าง กล่าวอีกนัยหนึ่ง "ความเท่าเทียมกัน" ควรถูกกำหนดไว้ในท้องถิ่นกับสถานที่ที่เรามีโคลนไหลมาจากทั้งสองด้านเพราะเราอาจไม่ต้องการที่ColoredPoint.equals(pt)จะล้มเหลวกับกรณีของPointแม้ว่าผู้เขียนColoredPointคิดว่ามันเป็นความคิดที่ดีเมื่อหกเดือนที่แล้ว .


6

เมื่อเทพการเขียนโปรแกรมเก่าได้ประดิษฐ์การเขียนโปรแกรมเชิงวัตถุด้วยคลาสพวกเขาตัดสินใจเมื่อมันมาถึงการจัดองค์ประกอบและการสืบทอดที่มีความสัมพันธ์สองอย่างสำหรับวัตถุ: "คือ" และ "มี"
สิ่งนี้สามารถแก้ไขปัญหาของคลาสย่อยที่แตกต่างจากคลาสพาเรนต์ แต่ทำให้สามารถใช้งานได้โดยไม่ต้องใช้รหัส เนื่องจากอินสแตนซ์คลาสย่อย "เป็น" วัตถุซูเปอร์คลาสและสามารถทดแทนได้โดยตรงสำหรับมันแม้ว่าคลาสย่อยจะมีฟังก์ชันสมาชิกหรือสมาชิกข้อมูลมากขึ้นก็ตาม "" มี "รับประกันว่ามันจะทำหน้าที่ทั้งหมดของพาเรนต์และมีทั้งหมด สมาชิก. ดังนั้นคุณอาจบอกว่า Point3D "เป็น" Point และ Point2D "คือ" Point หากทั้งคู่สืบทอดจาก Point นอกจากนี้ Point3D อาจเป็นคลาสย่อยของ Point2D

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

ดังนั้นคุณจะได้ตารางความเท่าเทียมกันที่แคบลง:

Both objects have same values, limited to subset of shared members

Child classes can be equal to parent classes if parent and childs
data members are the same.

Both objects entire data members are the same.

Objects must have all same values and be similar classes. 

Objects must have all same values and be the same class type. 

Equality is determined by specific logical conditions in the domain.

Only Objects that both point to same instance are equal. 

โดยทั่วไปคุณเลือกกฎที่เข้มงวดที่สุดที่คุณสามารถทำได้ที่จะยังคงทำหน้าที่ที่จำเป็นทั้งหมดในโดเมนปัญหาของคุณ การทดสอบความเท่าเทียมกันในตัวสำหรับตัวเลขได้รับการออกแบบให้มีข้อ จำกัด มากที่สุดเท่าที่จะเป็นไปได้สำหรับวัตถุประสงค์ทางคณิตศาสตร์ แต่โปรแกรมเมอร์มีหลายวิธีที่หากไม่ใช่เป้าหมายรวมถึงการปัดเศษขึ้น / ลงการตัดทอน gt, lt ฯลฯ . วัตถุที่มีการประทับเวลามักจะถูกเปรียบเทียบโดยเวลาการสร้างของพวกเขาและดังนั้นแต่ละอินสแตนซ์จะต้องไม่ซ้ำกันเพื่อการเปรียบเทียบได้เฉพาะ

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


2

กฎคือเมื่อใดก็ตามที่คุณแทนที่hashcode()คุณจะแทนที่equals()และในทางกลับกัน ไม่ว่าจะเป็นความคิดที่ดีหรือไม่นั้นขึ้นอยู่กับการใช้งานที่ต้องการ โดยส่วนตัวแล้วฉันจะใช้วิธีอื่น ( isLike()หรือคล้ายกัน) เพื่อให้ได้ผลที่เหมือนกัน


1
มันสามารถตกลงที่จะแทนที่ hashCode โดยไม่ต้องเอาชนะเท่ากับ ตัวอย่างเช่นเราจะทำเช่นนั้นเพื่อทดสอบอัลกอริทึมการแปลงแป้นพิมพ์ที่แตกต่างกันสำหรับเงื่อนไขความเท่าเทียมกันเดียวกัน
Patricia Shanahan

1

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

ตัวอย่างเช่นให้พิจารณาคลาสที่ห่อหุ้มเมทริกซ์ 2D ที่ไม่เปลี่ยนรูปของdoubleค่า หากวิธีการหนึ่งข้างนอกขอเมทริกซ์เอกลักษณ์ขนาด 1,000 วิธีที่สองขอเมทริกซ์แนวทแยงและผ่านอาร์เรย์ที่มี 1,000 รายการและวิธีที่สามขอเมทริกซ์ 2D และส่งอาร์เรย์ 1000x1000 ที่องค์ประกอบบนเส้นทแยงมุมหลักทั้งหมด 1.0 และอื่น ๆ ทั้งหมดเป็นศูนย์วัตถุที่มอบให้กับทั้งสามคลาสอาจใช้ร้านค้าสำรองที่แตกต่างกันภายใน [ครั้งแรกที่มีเขตข้อมูลเดียวสำหรับขนาดที่สองมีอาร์เรย์องค์ประกอบหนึ่งพันและที่สามมีอาร์เรย์ 1,000 องค์ประกอบองค์ประกอบ] แต่ ควรรายงานซึ่งกันและกันเท่ากัน [เนื่องจากทั้งสามแค็ปซูลเมทริกซ์ไม่เปลี่ยนรูป 1000x1000 กับเมทริกซ์แบบทแยงมุมและเลขศูนย์อื่น ๆ ]

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

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

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