Masalah ini paling jelas ketika Anda memiliki implementasi antarmuka yang berbeda, dan untuk tujuan koleksi tertentu Anda hanya peduli pada tampilan tingkat antarmuka objek. Misalnya, Anda memiliki antarmuka seperti ini:
public interface Person {
int getId();
}
Cara biasa untuk mengimplementasikan hashcode()
dan equals()
mengimplementasikan kelas akan memiliki kode seperti ini dalam equals
metode:
if (getClass() != other.getClass()) {
return false;
}
Ini menyebabkan masalah ketika Anda mencampur implementasi Person
di a HashMap
. Jika HashMap
satu-satunya peduli tentang tampilan tingkat antarmuka Person
, maka itu bisa berakhir dengan duplikat yang hanya berbeda dalam kelas implementasi mereka.
Anda dapat membuat kasus ini berfungsi dengan menggunakan equals()
metode liberal yang sama untuk semua implementasi, tetapi kemudian Anda berisiko equals()
melakukan hal yang salah dalam konteks yang berbeda (seperti membandingkan dua Person
s yang didukung oleh catatan basis data dengan nomor versi).
Intuisi saya memberi tahu saya bahwa kesetaraan harus didefinisikan per koleksi, bukan per kelas. Saat menggunakan koleksi yang mengandalkan pemesanan, Anda bisa menggunakan kebiasaan Comparator
untuk memilih pemesanan yang tepat di setiap konteks. Tidak ada analog untuk koleksi berbasis hash. Kenapa ini?
Hanya untuk memperjelas, pertanyaan ini berbeda dari " Mengapa .compareTo () dalam antarmuka sementara .equals () berada dalam kelas di Jawa? " Karena berkaitan dengan implementasi koleksi. compareTo()
dan equals()
/ hashcode()
keduanya menderita masalah universalitas saat menggunakan koleksi: Anda tidak dapat memilih fungsi perbandingan yang berbeda untuk koleksi yang berbeda. Jadi untuk keperluan pertanyaan ini, hierarki warisan suatu objek tidak penting sama sekali; yang penting adalah apakah fungsi perbandingan didefinisikan per objek atau per koleksi.
sumber
Person
menerapkan perilakuequals
dan yang diharapkanhashCode
. Anda kemudian akan memilikiHashMap<PersonWrapper, V>
. Ini adalah salah satu contoh di mana pendekatan murni-OOP tidak elegan: tidak setiap operasi pada objek masuk akal sebagai metode objek itu. SeluruhObject
tipe Java adalah gabungan dari tanggung jawab yang berbeda - hanyagetClass
,finalize
dantoString
metode yang tampaknya dapat dibenarkan dari praktik terbaik saat ini.IEqualityComparer<T>
koleksi berbasis hash. Jika Anda tidak menentukan satu, itu menggunakan implementasi default berdasarkanObject.Equals
danObject.GetHashCode()
. 2) Penimpaan IMOEquals
pada jenis referensi yang bisa berubah jarang merupakan ide yang bagus. Dengan cara itu kesetaraan standar cukup ketat, tetapi Anda dapat menggunakan aturan kesetaraan yang lebih santai saat Anda membutuhkannya melalui kebiasaanIEqualityComparer<T>
.Jawaban:
Desain ini kadang-kadang dikenal sebagai "Kesetaraan Universal", itu adalah keyakinan bahwa apakah dua hal sama atau tidak adalah properti universal.
Terlebih lagi, kesetaraan adalah properti dari dua objek, tetapi dalam OO, Anda selalu memanggil metode pada satu objek tunggal , dan objek itu hanya dapat memutuskan bagaimana menangani pemanggilan metode itu. Jadi, dalam desain seperti Java, di mana kesetaraan adalah properti dari salah satu dari dua objek yang dibandingkan, bahkan tidak mungkin untuk menjamin beberapa sifat dasar kesetaraan seperti simetri (
a == b
⇔b == a
), karena dalam kasus pertama, metode sedang dipanggila
dan dalam kasus kedua sedang dipanggilb
dan karena prinsip dasar OO, itu semataa
- mata keputusan (dalam kasus pertama) ataub
Keputusan (dalam kasus kedua) apakah itu menganggap dirinya sama dengan yang lain. Satu-satunya cara untuk mendapatkan simetri adalah dengan membuat dua objek bekerja sama, tetapi jika mereka tidak ... keberuntungan.Salah satu solusinya adalah membuat kesetaraan bukan properti dari satu objek, tetapi baik properti dari dua objek, atau properti dari objek ketiga. Opsi terakhir itu juga memecahkan masalah kesetaraan universal, karena jika Anda menjadikan kesetaraan sebagai properti dari objek "konteks" ketiga, maka Anda dapat membayangkan memiliki
EqualityComparer
objek yang berbeda untuk konteks yang berbeda.Ini adalah desain yang dipilih untuk Haskell, misalnya, dengan
Eq
typeclass. Ini juga desain yang dipilih oleh beberapa perpustakaan Scala pihak ketiga (ScalaZ, misalnya), tetapi bukan inti Scala atau perpustakaan standar, yang menggunakan persamaan universal untuk kompatibilitas dengan platform host yang mendasarinya.Menariknya, ini juga desain yang dipilih dengan Java
Comparable
/Comparator
interface. Para perancang Jawa jelas menyadari masalah itu, tetapi untuk beberapa alasan hanya menyelesaikannya untuk pemesanan, tetapi tidak untuk kesetaraan (atau hashing).Jadi, seperti pertanyaannya
jawabannya adalah "Saya tidak tahu". Jelas, para perancang Jawa menyadari masalah itu, sebagaimana dibuktikan dengan keberadaannya
Comparator
, tetapi mereka jelas tidak menganggapnya sebagai masalah kesetaraan dan hashing. Bahasa dan perpustakaan lain membuat pilihan yang berbeda.sumber
The methods equals and hashCode are declared for the benefit of hashtables such as java.util.Hashtable
yaitu keduanyaequals
danhashCode
diperkenalkan sebagaiObject
metode oleh Java devs semata - mata demiHashtable
- tidak ada gagasan UE atau apa pun silimar di mana pun dalam spesifikasi, dan kutipannya cukup jelas bagi saya; jika bukan karenaHashtable
,equals
mungkin sudah di antarmuka sepertiComparable
. Karena itu, meski sebelumnya saya yakin jawaban Anda benar, sekarang saya menganggapnya tidak berdasar.clone
- itu awalnya operator , bukan metode (lihat Spesifikasi Bahasa Oak), kutipan:The unary operator clone is applied to an object. (...) The clone operator is normally used inside new to clone the prototype of some class, before applying the initializers (constructors)
- tiga operator seperti kata kunci ituinstanceof new clone
(bagian 8.1, operator). Saya berasumsi bahwa itulah alasan (bersejarah) nyata dariclone
/Cloneable
mess -Cloneable
hanyalah sebuah penemuan di kemudian hari, danclone
kode yang ada diperbaiki.Jawaban nyata untuk
adalah, kutipan dari Josh Bloch :
Masalahnya terletak semata-mata dalam sejarah Jawa, seperti dengan hal-hal lain yang serupa, misalnya
.clone()
vsCloneable
.tl; dr
itu karena alasan historis terutama; perilaku / abstraksi saat ini diperkenalkan di JDK 1.0 dan tidak diperbaiki kemudian karena itu hampir tidak mungkin dilakukan dengan mempertahankan kompatibilitas kode mundur.
Pertama, mari kita simpulkan beberapa fakta Java yang terkenal:
Hashtable
,.hashCode()
&.equals()
diimplementasikan di JDK 1.0, ( Hashtable )Comparable
/Comparator
diperkenalkan di JDK 1.2 ( Sebanding ),Sekarang, ini sebagai berikut:
.hashCode()
&.equals()
untuk antarmuka yang berbeda sambil tetap mempertahankan kompatibilitas ke belakang setelah orang-orang menyadari ada abstraksi yang lebih baik daripada menempatkan mereka di superobject, karena misalnya masing-masing dan setiap programmer Java oleh 1,2 tahu bahwa setiapObject
memilikinya, dan mereka memiliki tinggal di sana secara fisik untuk memberikan kompatibilitas kode terkompilasi (JVM) juga - dan menambahkan antarmuka eksplisit untuk setiapObject
subkelas yang benar-benar menerapkannya akan membuat kekacauan ini sama (sic!) denganClonable
satu ( Bloch membahas mengapa Cloneable menyebalkan , juga dibahas dalam misalnya EJ 2 dan banyak tempat lain, termasuk SO),Sekarang, Anda mungkin bertanya "apa yang terjadi
Hashtable
dengan semua ini"?Jawabannya adalah:
hashCode()
/equals()
kontrak dan keterampilan desain bahasa yang tidak begitu baik dari pengembang Java inti pada 1995/1996.Kutipan dari Java 1.0 Language Spec, tanggal 1996 - 4.3.2 The Class
Object
, hal.41:(perhatikan pernyataan ini tepat telah berubah di versi, mengatakan, kutipan:
The method hashCode is very useful, together with the method equals, in hashtables such as java.util.HashMap.
, sehingga mustahil untuk membuat langsungHashtable
-hashCode
-equals
koneksi tanpa membaca JLS sejarah!)Tim Java memutuskan mereka ingin koleksi kamus-gaya yang baik, dan mereka menciptakan
Hashtable
(ide bagus sejauh ini), tetapi mereka ingin programmer untuk dapat menggunakannya dengan kode sesedikit mungkin / kurva belajar (oops! Kesulitan masuk!) - dan, karena tidak ada obat generik belum [itu JDK 1.0 setelah semua], itu berarti bahwa baik setiapObject
put keHashtable
harus secara eksplisit mengimplementasikan beberapa antarmuka (interface dan masih hanya di awal mereka saat itu ... tidak adaComparable
belum bahkan!) , membuat ini jera untuk menggunakannya untuk banyak - atauObject
harus implisit menerapkan beberapa metode hashing.Jelas, mereka pergi dengan solusi 2, karena alasan yang diuraikan di atas. Yup, sekarang kita tahu mereka salah. ... mudah untuk menjadi pintar di belakang. tertawa kecil
Sekarang,
hashCode()
mengharuskan setiap objek yang memilikinya harus memilikiequals()
metode yang berbeda - jadi itu cukup jelas yangequals()
harus dimasukkanObject
juga.Karena implementasi default dari metode-metode tersebut pada valid
a
&b
Object
s pada dasarnya tidak berguna dengan menjadi berlebihan (membuata.equals(b)
sama dengana==b
dana.hashCode() == b.hashCode()
kira-kira sama dengana==b
juga, kecualihashCode
dan / atauequals
ditimpa, atau Anda GC ratusan ribuObject
s selama siklus hidup aplikasi Anda 1 ) , aman untuk mengatakan mereka disediakan terutama sebagai ukuran cadangan dan untuk kenyamanan penggunaan. Ini adalah persis bagaimana kita sampai pada fakta terkenal yang selalu menimpa keduanya.equals()
&.hashCode()
jika Anda berniat benar-benar membandingkan objek atau menyimpan hash. Mengganti hanya satu dari mereka tanpa yang lain adalah cara yang baik untuk mengacaukan kode Anda (dengan membandingkan hasil yang jahat atau nilai tabrakan bucket yang sangat tinggi) - dan menggerakkan kepala Anda di sekitarnya merupakan sumber kebingungan & kesalahan konstan untuk pemula (cari SO untuk melihat untuk Anda sendiri) dan gangguan terus-menerus ke yang lebih berpengalaman.Juga, perhatikan bahwa meskipun C # berurusan dengan sama dengan & kode hash dalam cara yang sedikit lebih baik, Eric Lippert sendiri menyatakan bahwa mereka melakukan kesalahan yang hampir sama dengan C # yang Sun lakukan dengan Jawa tahun sebelum dimulainya C # :
1 tentu saja,
Object#hashCode
masih bisa bertabrakan, tetapi perlu sedikit usaha untuk melakukan itu, lihat: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6809470 dan laporan bug yang ditautkan untuk detail; /programming/1381060/hashcode-uniqueness/1381114#1381114 membahas subjek ini lebih mendalam.sumber
Object
desain; hashability adalah.