Jawaban Kilian Foth sangat bagus. Saya hanya ingin menambahkan contoh kanonik * mengapa ini menjadi masalah. Bayangkan kelas Point integer:
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);
}
}
Sekarang mari kita sub-kelas menjadi titik 3D.
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);
}
}
Sangat sederhana! Mari kita gunakan poin kami:
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
Anda mungkin bertanya-tanya mengapa saya memposting contoh yang begitu mudah. Inilah tangkapannya:
p2a.equals(p3a); // true
p3a.equals(p2a); // FALSE!
Ketika kita membandingkan titik 2D ke titik 3D yang setara, kita menjadi benar, tetapi ketika kita membalikkan perbandingan, kita menjadi salah (karena p2a gagal instanceof Point3D
).
Kesimpulan
Biasanya dimungkinkan untuk mengimplementasikan metode dalam subclass sedemikian rupa sehingga tidak lagi kompatibel dengan bagaimana kelas-super mengharapkannya bekerja.
Secara umum tidak mungkin untuk menerapkan equals () pada subclass yang sangat berbeda dengan cara yang kompatibel dengan kelas induknya.
Ketika Anda menulis sebuah kelas yang ingin Anda izinkan orang lain untuk membuat subkelas, itu ide yang sangat bagus untuk menulis kontrak untuk bagaimana masing-masing metode harus berperilaku. Yang lebih baik lagi adalah serangkaian unit test yang dapat dijalankan oleh orang-orang terhadap penerapan metode yang diganti untuk membuktikan bahwa mereka tidak melanggar kontrak. Hampir tidak ada yang melakukan itu karena terlalu banyak pekerjaan. Tetapi jika Anda peduli, itulah yang harus dilakukan.
Contoh bagus dari kontrak yang dieja dengan baik adalah Comparator . Abaikan saja apa yang dikatakannya .equals()
karena alasan yang dijelaskan di atas. Berikut adalah contoh tentang bagaimana Pembanding dapat melakukan hal-hal yang .equals()
tidak bisa .
Catatan
"Java Efektif" Item 8 dari Josh Bloch adalah sumber dari contoh ini, tetapi Bloch menggunakan ColorPoint yang menambahkan warna alih-alih sumbu ketiga dan menggunakan ganda sebagai ganti int. Contoh Java Bloch pada dasarnya digandakan oleh Odersky / Spoon / Venners yang membuat contoh mereka tersedia secara online.
Beberapa orang keberatan dengan contoh ini karena jika Anda membiarkan kelas induk tahu tentang sub-kelas, Anda dapat memperbaiki masalah ini. Itu benar jika ada sejumlah kecil sub-kelas dan jika orang tua tahu tentang mereka semua. Tetapi pertanyaan aslinya adalah tentang membuat API yang akan digunakan orang lain untuk membuat sub-kelas. Dalam hal ini, Anda biasanya tidak dapat memperbarui implementasi induk agar kompatibel dengan sub-kelas.
Bonus
Komparator juga menarik karena bekerja di sekitar masalah penerapan equals () dengan benar. Lebih baik lagi, ini mengikuti pola untuk memperbaiki masalah pewarisan jenis ini: pola desain Strategi. Typeclasses yang membuat orang Haskell dan Scala bersemangat juga merupakan pola Strategi. Warisan tidak buruk atau salah, itu hanya rumit. Untuk bacaan lebih lanjut, bacalah makalah Philip Wadler Bagaimana membuat polimorfisme ad-hoc menjadi lebih sedikit