คำตอบของ Kilian Foth นั้นยอดเยี่ยม ฉันแค่ต้องการเพิ่มตัวอย่างตามบัญญัติ * ของสาเหตุที่เป็นปัญหา ลองนึกภาพคลาสพอยต์จำนวนเต็ม:
class Point2D {
public int x;
public int y;
// constructor
public Point2D(int theX, int theY) { x = theX; y = theY; }
public int hashCode() { return x + y; }
public boolean equals(Object o) {
if (this == o) { return true; }
if ( !(o instanceof Point2D) ) { return false; }
Point2D that = (Point2D) o;
return (x == that.x) &&
(y == that.y);
}
}
ทีนี้ลองซับคลาสให้เป็นจุด 3 มิติ
class Point3D extends Point2D {
public int z;
// constructor
public Point3D(int theX, int theY, int theZ) {
super(x, y); z = theZ;
}
public int hashCode() { return super.hashCode() + z; }
public boolean equals(Object o) {
if (this == o) { return true; }
if ( !(o instanceof Point3D) ) { return false; }
Point3D that = (Point3D) o;
return super.equals(that) &&
(z == that.z);
}
}
ง่ายสุด ๆ ! ลองใช้คะแนนของเรา:
Point2D p2a = new Point2D(3, 5);
Point2D p2b = new Point2D(3, 5);
Point2D p2c = new Point2D(3, 7);
p2a.equals(p2b); // true
p2b.equals(p2a); // true
p2a.equals(p2c); // false
Point3D p3a = new Point3D(3, 5, 7);
Point3D p3b = new Point3D(3, 5, 7);
Point3D p3c = new Point3D(3, 7, 11);
p3a.equals(p3b); // true
p3b.equals(p3a); // true
p3a.equals(p3c); // false
คุณอาจสงสัยว่าทำไมฉันโพสต์ตัวอย่างง่าย ๆ นี่คือการจับ:
p2a.equals(p3a); // true
p3a.equals(p2a); // FALSE!
เมื่อเราเปรียบเทียบจุด 2D กับจุด 3D ที่เทียบเท่าเราจะได้รับจริง แต่เมื่อเราย้อนกลับการเปรียบเทียบเราจะได้รับเท็จ (เนื่องจาก p2a ล้มเหลวinstanceof Point3D
)
ข้อสรุป
โดยทั่วไปแล้วเป็นไปได้ที่จะใช้วิธีการในคลาสย่อยในลักษณะที่ไม่เข้ากันได้กับวิธีการที่ซูเปอร์คลาสคาดว่ามันจะทำงานได้อีกต่อไป
โดยทั่วไปแล้วจะไม่สามารถใช้เท่ากับ () ในคลาสย่อยที่แตกต่างกันอย่างมีนัยสำคัญในวิธีที่เข้ากันได้กับคลาสแม่
เมื่อคุณเขียนชั้นเรียนที่คุณตั้งใจจะอนุญาตให้คนอื่นทำ subclass คุณควรเขียนสัญญาสำหรับวิธีการทำงานของแต่ละวิธี ยิ่งไปกว่านั้นก็คือชุดทดสอบหน่วยที่ผู้คนสามารถใช้กับการใช้งานวิธีการแทนที่เพื่อพิสูจน์ว่าพวกเขาไม่ได้ละเมิดสัญญา เกือบจะไม่มีใครทำอย่างนั้นเพราะมันทำงานได้มากเกินไป แต่ถ้าคุณใส่ใจนั่นเป็นสิ่งที่ต้องทำ
ตัวอย่างที่ดีของการทำสัญญาที่ดีสะกดออกมาเป็นตัวเปรียบเทียบ เพียงเพิกเฉยสิ่งที่พูดเกี่ยวกับ.equals()
สาเหตุที่อธิบายไว้ข้างต้น นี่คือตัวอย่างของวิธีการเปรียบเทียบสามารถทำสิ่ง.equals()
ไม่สามารถ
หมายเหตุ
"Effective Java" ของ Josh Bloch รายการที่ 8 เป็นที่มาของตัวอย่างนี้ แต่ Bloch ใช้ ColorPoint ซึ่งเพิ่มสีแทนแกนที่สามและใช้ double เป็นคู่แทน ints ตัวอย่าง Java ของ Bloch นั้นมีการทำซ้ำโดยOdersky / Spoon / Vennersซึ่งทำให้ตัวอย่างของพวกเขาพร้อมใช้งานออนไลน์
มีหลายคนที่คัดค้านตัวอย่างนี้เพราะหากคุณให้ผู้ปกครองทราบเกี่ยวกับคลาสย่อยคุณสามารถแก้ไขปัญหานี้ได้ นั่นเป็นเรื่องจริงถ้ามีคลาสย่อยจำนวนน้อยพอและถ้าผู้ปกครองรู้เกี่ยวกับพวกเขาทั้งหมด แต่คำถามเดิมเกี่ยวกับการสร้าง API ซึ่งคนอื่นจะเขียนคลาสย่อยสำหรับ ในกรณีดังกล่าวโดยทั่วไปคุณไม่สามารถอัปเดตการใช้งานหลักเพื่อให้เข้ากันได้กับคลาสย่อย
โบนัส
เครื่องมือเปรียบเทียบยังน่าสนใจเพราะสามารถแก้ไขปัญหาของการใช้งานเท่ากับ () ได้อย่างถูกต้อง ยังดีกว่าตามรูปแบบสำหรับการแก้ไขปัญหาการสืบทอดประเภทนี้: รูปแบบการออกแบบกลยุทธ์ ประเภท Typeclasses ที่คน Haskell และ Scala รู้สึกตื่นเต้นก็เป็นกลยุทธ์เช่นกัน การรับมรดกไม่ได้เลวหรือผิดมันเป็นเรื่องยุ่งยาก สำหรับการอ่านเพิ่มเติมให้ดูที่กระดาษของ Philip Wadler วิธีทำให้ความแตกต่าง ad-hoc เฉพาะกิจน้อยลง