equals
berkait hashCode
rapat antara satu sama lain, dan dinasihatkan untuk mengatasi kedua-dua kaedah ini dalam kelas mereka secara konsisten. Sebilangan kecil yang sedikit tahu mengapa ini berlaku dan apakah akibat yang menyedihkan yang boleh berlaku jika peraturan ini dilanggar. Saya bercadang untuk mempertimbangkan konsep kaedah ini, ulangi tujuan mereka dan faham mengapa ia sangat berkaitan. Saya menulis artikel ini, seperti artikel sebelumnya tentang memuatkan kelas, untuk diri saya sendiri agar akhirnya mendedahkan semua butiran isu dan tidak lagi kembali kepada sumber pihak ketiga. Oleh itu, saya dengan senang hati menerima kritikan yang membina, kerana jika terdapat jurang di suatu tempat, ia harus dihapuskan. Artikel itu, sayangnya, ternyata agak panjang.
sama dengan peraturan mengatasi
Kaedahequals()
diperlukan dalam Java untuk mengesahkan atau menafikan fakta bahawa dua objek dari asal yang sama secara logiknya sama . Iaitu, apabila membandingkan dua objek, pengaturcara perlu memahami sama ada medan pentingnya adalah setara . Tidak semestinya semua medan mestilah sama, kerana kaedah tersebut equals()
membayangkan kesamaan logik . Tetapi kadang-kadang tidak ada keperluan khusus untuk menggunakan kaedah ini. Seperti yang mereka katakan, cara paling mudah untuk mengelakkan masalah menggunakan mekanisme tertentu adalah dengan tidak menggunakannya. Perlu diingatkan juga bahawa sebaik sahaja anda memecahkan kontrak, equals
anda kehilangan kawalan untuk memahami cara objek dan struktur lain akan berinteraksi dengan objek anda. Dan seterusnya mencari punca ralat akan menjadi sangat sukar.
Bila tidak boleh mengatasi kaedah ini
- Apabila setiap contoh kelas adalah unik. Pada tahap yang lebih besar, ini terpakai kepada kelas yang menyediakan gelagat khusus dan bukannya direka bentuk untuk berfungsi dengan data. Seperti, sebagai contoh, sebagai kelas
- Sebenarnya kelas tidak diperlukan untuk menentukan kesetaraan kejadiannya. Sebagai contoh, untuk kelas
- Apabila kelas yang anda lanjutkan sudah mempunyai pelaksanaan kaedahnya sendiri
equals
dan tingkah laku pelaksanaan ini sesuai dengan anda. Sebagai contoh, untuk kelas - Dan akhirnya, tidak perlu untuk mengatasi
equals
apabila skop kelas anda adalahprivate
ataupackage-private
dan anda pasti bahawa kaedah ini tidak akan dipanggil.
Thread
. Bagi mereka equals
, pelaksanaan kaedah yang disediakan oleh kelas Object
adalah lebih daripada mencukupi. Contoh lain ialah kelas enum ( Enum
).
java.util.Random
tidak perlu sama sekali untuk membandingkan kejadian kelas antara satu sama lain, menentukan sama ada mereka boleh mengembalikan urutan nombor rawak yang sama. Semata-mata kerana sifat kelas ini tidak menunjukkan kelakuan sedemikian.
Set
, List
, Map
pelaksanaan equals
adalah dalam AbstractSet
, AbstractList
dan AbstractMap
masing-masing.
sama dengan kontrak
Apabila mengatasi kaedah,equals
pembangun mesti mematuhi peraturan asas yang ditakrifkan dalam spesifikasi bahasa Java.
- Reflekstiviti untuk sebarang nilai tertentu
- simetri untuk mana-mana nilai yang diberikan
- Transitiviti untuk mana-mana nilai yang diberikan
- Konsisten untuk sebarang nilai yang diberikan,
- Perbandingan null untuk sebarang nilai tertentu
x
, ungkapan x.equals(x)
mesti kembali true
.
Memandangkan - maksud sedemikian
x != null
x
dan y
, x.equals(y)
harus kembali true
hanya jika ia y.equals(x)
kembali true
.
x
, y
dan z
, jika x.equals(y)
pulangan true
dan y.equals(z)
pulangan true
, x.equals(z)
mesti mengembalikan nilai tersebut true
.
x
dan y
panggilan berulang x.equals(y)
akan mengembalikan nilai panggilan sebelumnya kepada kaedah ini, dengan syarat medan yang digunakan untuk membandingkan dua objek tidak berubah antara panggilan.
x
panggilan x.equals(null)
mesti kembali false
.
sama dengan pelanggaran kontrak
Banyak kelas, seperti dari Rangka Kerja Koleksi Java, bergantung pada pelaksanaan kaedahequals()
, jadi anda tidak boleh mengabaikannya, kerana Pelanggaran kontrak kaedah ini boleh membawa kepada operasi aplikasi yang tidak rasional, dan dalam kes ini agak sukar untuk mencari sebabnya. Mengikut prinsip reflekstiviti , setiap objek mestilah setara dengan dirinya sendiri. Jika prinsip ini dilanggar, apabila kita menambah objek pada koleksi dan kemudian mencarinya menggunakan kaedah, contains()
kita tidak akan dapat mencari objek yang baru kita tambahkan pada koleksi. Keadaan simetri menyatakan bahawa mana-mana dua objek mestilah sama tanpa mengira susunan di mana ia dibandingkan. Sebagai contoh, jika anda mempunyai kelas yang mengandungi hanya satu medan jenis rentetan, adalah tidak betul untuk membandingkan equals
medan ini dengan rentetan dalam kaedah. Kerana dalam kes perbandingan terbalik, kaedah akan sentiasa mengembalikan nilai false
.
// Нарушение симметричности
public class SomeStringify {
private String s;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof SomeStringify) {
return s.equals(((SomeStringify) o).s);
}
// нарушение симметричности, классы разного происхождения
if (o instanceof String) {
return s.equals(o);
}
return false;
}
}
//Правильное определение метода equals
@Override
public boolean equals(Object o) {
if (this == o) return true;
return o instanceof SomeStringify &&
((SomeStringify) o).s.equals(s);
}
Daripada keadaan transitiviti ia mengikuti bahawa jika mana-mana dua daripada tiga objek adalah sama, maka dalam kes ini ketiga-tiganya mestilah sama. Prinsip ini boleh dilanggar dengan mudah apabila perlu melanjutkan kelas asas tertentu dengan menambahkan komponen yang bermakna padanya . Sebagai contoh, ke kelas Point
dengan koordinat x
dan y
anda perlu menambah warna titik dengan mengembangkannya. Untuk melakukan ini, anda perlu mengisytiharkan kelas ColorPoint
dengan medan yang sesuai color
. Oleh itu, jika dalam kelas lanjutan kita memanggil equals
kaedah induk, dan dalam induk kita menganggap bahawa hanya koordinat x
dan dibandingkan y
, maka dua titik warna yang berbeza tetapi dengan koordinat yang sama akan dianggap sama, yang tidak betul. Dalam kes ini, adalah perlu untuk mengajar kelas yang diperolehi untuk membezakan warna. Untuk melakukan ini, anda boleh menggunakan dua kaedah. Tetapi seseorang akan melanggar peraturan simetri , dan kedua - transitivity .
// Первый способ, нарушая симметричность
// Метод переопределен в классе ColorPoint
@Override
public boolean equals(Object o) {
if (!(o instanceof ColorPoint)) return false;
return super.equals(o) && ((ColorPoint) o).color == color;
}
Dalam kes ini, panggilan point.equals(colorPoint)
akan mengembalikan nilai true
, dan perbandingan colorPoint.equals(point)
akan kembali false
, kerana menjangkakan objek kelas "nya". Oleh itu, peraturan simetri dilanggar. Kaedah kedua melibatkan melakukan semakan "buta" dalam kes apabila tiada data tentang warna titik, iaitu kita mempunyai kelas Point
. Atau semak warna jika maklumat mengenainya tersedia, iaitu, bandingkan objek kelas ColorPoint
.
// Метод переопределен в классе ColorPoint
@Override
public boolean equals(Object o) {
if (!(o instanceof Point)) return false;
// Слепая проверка
if (!(o instanceof ColorPoint))
return super.equals(o);
// Полная проверка, включая цвет точки
return super.equals(o) && ((ColorPoint) o).color == color;
}
Prinsip transitivity dilanggar di sini seperti berikut. Katakan terdapat definisi bagi objek berikut:
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
Oleh itu, walaupun kesamaan p1.equals(p2)
dan berpuas hati p2.equals(p3)
, p1.equals(p3)
ia akan mengembalikan nilai false
. Pada masa yang sama, kaedah kedua, pada pendapat saya, kelihatan kurang menarik, kerana Dalam sesetengah kes, algoritma mungkin buta dan tidak melakukan perbandingan sepenuhnya, dan anda mungkin tidak mengetahuinya. Sedikit puisi Secara umum, seperti yang saya fahami, tidak ada penyelesaian konkrit untuk masalah ini. Terdapat pendapat daripada seorang pengarang berwibawa bernama Kay Horstmann bahawa anda boleh menggantikan penggunaan operator instanceof
dengan panggilan kaedah getClass()
yang mengembalikan kelas objek dan, sebelum anda mula membandingkan objek itu sendiri, pastikan ia adalah daripada jenis yang sama , dan tidak memberi perhatian kepada fakta asal usul mereka yang sama. Oleh itu, peraturan simetri dan transitiviti akan dipenuhi. Tetapi pada masa yang sama, di seberang barikade berdiri seorang lagi pengarang, tidak kurang dihormati dalam kalangan luas, Joshua Bloch, yang percaya bahawa pendekatan ini melanggar prinsip penggantian Barbara Liskov. Prinsip ini menyatakan bahawa "kod panggilan mesti merawat kelas asas dengan cara yang sama seperti subkelasnya tanpa mengetahuinya . " Dan dalam penyelesaian yang dicadangkan oleh Horstmann, prinsip ini jelas dilanggar, kerana ia bergantung pada pelaksanaannya. Pendek kata, jelas perkara itu gelap. Perlu juga diperhatikan bahawa Horstmann menjelaskan peraturan untuk menggunakan pendekatannya dan menulis dalam bahasa Inggeris yang jelas bahawa anda perlu memutuskan strategi semasa mereka bentuk kelas, dan jika ujian kesamaan akan dijalankan hanya oleh superclass, anda boleh melakukan ini dengan melakukan operasi itu instanceof
. Jika tidak, apabila semantik semakan berubah bergantung pada kelas terbitan dan pelaksanaan kaedah perlu dipindahkan ke bawah hierarki, anda mesti menggunakan kaedah getClass()
. Joshua Bloch, sebaliknya, mencadangkan untuk meninggalkan warisan dan menggunakan komposisi objek dengan memasukkan ColorPoint
kelas dalam kelas Point
dan menyediakan kaedah akses asPoint()
untuk mendapatkan maklumat secara khusus tentang titik itu. Ini akan mengelakkan melanggar semua peraturan, tetapi, pada pendapat saya, ia akan menjadikan kod lebih sukar untuk difahami. Pilihan ketiga ialah menggunakan penjanaan automatik kaedah sama dengan menggunakan IDE. Idea, dengan cara ini, menghasilkan semula generasi Horstmann, membolehkan anda memilih strategi untuk melaksanakan kaedah dalam superclass atau dalam keturunannya. Akhir sekali, peraturan ketekalan seterusnya menyatakan bahawa walaupun objek x
tidak y
berubah, memanggilnya semula x.equals(y)
mesti mengembalikan nilai yang sama seperti sebelumnya. Peraturan terakhir ialah tiada objek harus sama dengan null
. Semuanya jelas di sini null
- ini adalah ketidakpastian, adakah objek sama dengan ketidakpastian? Ia tidak jelas, iaitu false
.
Algoritma umum untuk menentukan sama
- Semak kesamaan rujukan objek
this
dan parameter kaedaho
.if (this == o) return true;
- Semak sama ada pautan ditakrifkan
o
, iaitu sama ada ianull
.
Jika pada masa hadapan, apabila membandingkan jenis objek, operator akan digunakaninstanceof
, item ini boleh dilangkau, kerana parameter ini kembalifalse
dalam kes ininull instanceof Object
. - Bandingkan jenis objek
this
menggunakano
operatorinstanceof
atau kaedahgetClass()
, berpandukan penerangan di atas dan gerak hati anda sendiri. - Jika kaedah
equals
diganti dalam subkelas, pastikan anda membuat panggilansuper.equals(o)
- Tukar jenis parameter
o
kepada kelas yang diperlukan. - Lakukan perbandingan semua medan objek penting:
- untuk jenis primitif (kecuali
float
dandouble
), menggunakan operator==
- untuk medan rujukan anda perlu memanggil kaedah mereka
equals
- untuk tatasusunan, anda boleh menggunakan lelaran kitaran atau kaedah
Arrays.equals()
- untuk jenis
float
dandouble
perlu menggunakan kaedah perbandingan kelas pembalut yang sepadanFloat.compare()
danDouble.compare()
- untuk jenis primitif (kecuali
- Dan akhirnya, jawab tiga soalan: adakah kaedah yang dilaksanakan simetri ? Transitif ? Setuju ? Dua prinsip lain ( reflekstiviti dan kepastian ) biasanya dijalankan secara automatik.
Peraturan menimpa HashCode
Hash ialah nombor yang dijana daripada objek yang menerangkan keadaannya pada satu ketika. Nombor ini digunakan di Jawa terutamanya dalam jadual cincang sepertiHashMap
. Dalam kes ini, fungsi cincang untuk mendapatkan nombor berdasarkan objek mesti dilaksanakan dengan cara untuk memastikan pengagihan elemen yang agak sekata merentas jadual cincang. Dan juga untuk meminimumkan kemungkinan perlanggaran apabila fungsi mengembalikan nilai yang sama untuk kunci yang berbeza.
Kod cincang kontrak
Untuk melaksanakan fungsi cincang, spesifikasi bahasa mentakrifkan peraturan berikut:- memanggil kaedah
hashCode
lebih daripada sekali pada objek yang sama mesti mengembalikan nilai cincang yang sama, dengan syarat medan objek yang terlibat dalam pengiraan nilai tidak berubah. - memanggil kaedah
hashCode
pada dua objek harus sentiasa mengembalikan nombor yang sama jika objek adalah sama (memanggil kaedahequals
pada objek ini mengembalikantrue
). - memanggil kaedah
hashCode
pada dua objek yang tidak sama mesti mengembalikan nilai cincang yang berbeza. Walaupun keperluan ini tidak wajib, perlu dipertimbangkan bahawa pelaksanaannya akan memberi kesan positif ke atas prestasi jadual cincang.
Kaedah equals dan hashCode mesti diatasi bersama
Berdasarkan kontrak yang diterangkan di atas, berikutan bahawa apabila mengatasi kaedah dalam kod andaequals
, anda mesti sentiasa mengatasi kaedah tersebut hashCode
. Oleh kerana sebenarnya dua contoh kelas adalah berbeza kerana ia berada dalam kawasan ingatan yang berbeza, ia perlu dibandingkan mengikut beberapa kriteria logik. Sehubungan itu, dua objek yang setara secara logik mesti mengembalikan nilai cincang yang sama. Apakah yang berlaku jika hanya satu daripada kaedah ini ditindih?
-
equals
YahashCode
tidakKatakan kami mentakrifkan kaedah
equals
dalam kelas kami dengan betul, danhashCode
memutuskan untuk meninggalkan kaedah seperti yang terdapat dalam kelasObject
. Kemudian dari sudut pandangan kaedahequals
kedua-dua objek akan sama secara logik, manakala dari sudut pandangan kaedahhashCode
mereka tidak akan mempunyai persamaan. Oleh itu, dengan meletakkan objek dalam jadual cincang, kami menghadapi risiko tidak mendapatkannya kembali dengan kunci.
Sebagai contoh, seperti ini:Map<Point, String> m = new HashMap<>(); m.put(new Point(1, 1), “Point A”); // pointName == null String pointName = m.get(new Point(1, 1));
Jelas sekali, objek yang diletakkan dan objek yang dicari adalah dua objek berbeza, walaupun secara logiknya sama. Tapi sebab mereka mempunyai nilai cincang yang berbeza kerana kami melanggar kontrak, kami boleh mengatakan bahawa kami kehilangan objek kami di suatu tempat di perut jadual cincang.
-
hashCode
Yaequals
tidak.Apakah yang berlaku jika kita mengatasi kaedah
hashCode
danequals
mewarisi pelaksanaan kaedah daripada kelasObject
. Seperti yang anda ketahui,equals
kaedah lalai hanya membandingkan penunjuk dengan objek, menentukan sama ada ia merujuk kepada objek yang sama. Mari kita anggap bahawahashCode
kita telah menulis kaedah mengikut semua kanun, iaitu, menghasilkannya menggunakan IDE, dan ia akan mengembalikan nilai hash yang sama untuk objek yang sama secara logik. Jelas sekali, dengan berbuat demikian kami telah mentakrifkan beberapa mekanisme untuk membandingkan dua objek.Oleh itu, contoh daripada perenggan sebelumnya harus secara teori dijalankan. Tetapi kami masih tidak akan dapat mencari objek kami dalam jadual cincang. Walaupun kita akan hampir dengan ini, kerana sekurang-kurangnya kita akan menemui bakul meja hash di mana objek itu akan terletak.
Untuk berjaya mencari objek dalam jadual hash, selain membandingkan nilai hash kunci, penentuan kesamaan logik kunci dengan objek yang dicari juga digunakan. Iaitu,
equals
tiada cara untuk dilakukan tanpa mengatasi kaedah tersebut.
Algoritma umum untuk menentukan Kod hash
Di sini, nampaknya saya, anda tidak perlu terlalu risau dan menjana kaedah dalam IDE kegemaran anda. Kerana semua anjakan bit ini ke kanan dan kiri untuk mencari nisbah emas, iaitu taburan normal - ini adalah untuk lelaki yang degil sepenuhnya. Secara peribadi, saya ragu bahawa saya boleh melakukan lebih baik dan lebih pantas daripada Idea yang sama.Daripada kesimpulan
Oleh itu, kita melihat bahawa kaedahequals
memainkan hashCode
peranan yang jelas dalam bahasa Java dan direka untuk mendapatkan ciri kesamaan logik dua objek. Dalam kes kaedah, equals
ini mempunyai hubungan langsung dengan membandingkan objek, dalam kes hashCode
tidak langsung, apabila perlu, katakan, untuk menentukan lokasi anggaran objek dalam jadual cincang atau struktur data yang serupa untuk meningkatkan kelajuan mencari objek. Selain kontrak , equals
terdapat hashCode
satu lagi keperluan yang berkaitan dengan perbandingan objek. Ini ialah ketekalan kaedah compareTo
antara muka Comparable
dengan equals
. Keperluan ini mewajibkan pembangun untuk sentiasa kembali x.equals(y) == true
apabila x.compareTo(y) == 0
. Iaitu, kita melihat bahawa perbandingan logik dua objek tidak boleh bercanggah di mana-mana dalam aplikasi dan harus sentiasa konsisten.
GO TO FULL VERSION