equals
berkaitan hashCode
erat satu sama lain, dan disarankan untuk mengganti kedua metode ini di kelas mereka secara konsisten. Hanya sedikit orang yang mengetahui mengapa hal ini terjadi dan dampak buruk apa yang dapat terjadi jika aturan ini dilanggar. Saya mengusulkan untuk mempertimbangkan konsep metode-metode ini, mengulangi tujuannya dan memahami mengapa metode-metode tersebut begitu terhubung. Saya menulis artikel ini, seperti artikel sebelumnya tentang memuat kelas, untuk diri saya sendiri agar akhirnya mengungkapkan semua detail masalahnya dan tidak lagi kembali ke sumber pihak ketiga. Oleh karena itu, saya dengan senang hati menerima kritik yang membangun, karena jika ada kesenjangan, sebaiknya dihilangkan. Sayangnya, artikelnya ternyata cukup panjang.
sama dengan mengesampingkan aturan
Sebuah metodeequals()
diperlukan di Java untuk mengkonfirmasi atau menyangkal fakta bahwa dua objek dari asal yang sama secara logis sama . Artinya, ketika membandingkan dua objek, pemrogram perlu memahami apakah bidang signifikannya setara . Tidak semua bidang harus identik, karena metode ini equals()
menyiratkan persamaan logis . Namun terkadang tidak ada kebutuhan khusus untuk menggunakan metode ini. Seperti yang mereka katakan, cara termudah untuk menghindari masalah dengan menggunakan mekanisme tertentu adalah dengan tidak menggunakannya. Perlu juga dicatat bahwa setelah Anda melanggar kontrak, equals
Anda kehilangan kendali atas pemahaman bagaimana objek dan struktur lain akan berinteraksi dengan objek Anda. Dan selanjutnya akan sangat sulit untuk menemukan penyebab kesalahan tersebut.
Kapan tidak mengesampingkan metode ini
- Ketika setiap instance kelas adalah unik. Pada tingkat yang lebih luas, hal ini berlaku untuk kelas-kelas yang menyediakan perilaku spesifik, dan bukan dirancang untuk bekerja dengan data. Seperti misalnya kelas
- Padahal sebenarnya kelas tersebut tidak diharuskan untuk menentukan kesetaraan instance-nya. Misalnya, untuk suatu kelas,
- Ketika kelas yang Anda perluas sudah memiliki implementasi metodenya sendiri
equals
dan perilaku implementasi ini cocok untuk Anda. Misalnya untuk kelas - Dan terakhir, tidak perlu melakukan override
equals
ketika cakupan kelas Anda beradaprivate
ataupackage-private
dan Anda yakin metode ini tidak akan pernah dipanggil.
Thread
. Bagi mereka equals
, penerapan metode yang disediakan kelas Object
sudah lebih dari cukup. Contoh lainnya adalah kelas enum ( Enum
).
java.util.Random
tidak perlu sama sekali membandingkan instance kelas satu sama lain, menentukan apakah instance tersebut dapat mengembalikan urutan angka acak yang sama. Hanya karena sifat kelas ini tidak menyiratkan perilaku seperti itu.
Set
, List
, Map
implementasinya equals
ada di AbstractSet
, AbstractList
dan AbstractMap
masing-masing.
sama dengan kontrak
Saat mengganti suatu metode,equals
pengembang harus mematuhi aturan dasar yang ditentukan dalam spesifikasi bahasa Java.
- Refleksivitas untuk nilai tertentu
- Simetri untuk nilai apa pun
- Transitivitas untuk setiap nilai tertentu
- Konsistensi untuk nilai apa pun,
- Perbandingan nol untuk nilai tertentu,
x
, ekspresi x.equals(x)
harus kembali true
.
Diberikan - artinya seperti itu
x != null
x
dan y
, x.equals(y)
harus dikembalikan true
hanya jika ia y.equals(x)
kembali true
.
x
, y
dan z
, jika x.equals(y)
kembali true
dan y.equals(z)
kembali true
, x.equals(z)
harus mengembalikan nilai tersebut true
.
x
dan y
panggilan berulang x.equals(y)
akan mengembalikan nilai panggilan sebelumnya ke metode ini, asalkan bidang yang digunakan untuk membandingkan dua objek tidak berubah di antara panggilan.
x
panggilan x.equals(null)
harus kembali false
.
sama dengan pelanggaran kontrak
Banyak kelas, misalnya dari Java Collections Framework, bergantung pada implementasi metode iniequals()
, jadi Anda tidak boleh mengabaikannya, karena Pelanggaran terhadap kontrak metode ini dapat menyebabkan pengoperasian aplikasi yang tidak rasional, dan dalam hal ini akan cukup sulit untuk menemukan alasannya. Menurut prinsip refleksivitas , setiap benda harus setara dengan dirinya sendiri. Jika prinsip ini dilanggar, maka ketika kita menambahkan suatu objek ke dalam koleksi dan kemudian mencarinya menggunakan metode tersebut, contains()
kita tidak akan dapat menemukan objek yang baru saja kita tambahkan ke dalam koleksi tersebut. Syarat simetri menyatakan bahwa dua benda harus sama, tidak peduli urutan perbandingannya. Misalnya, jika Anda memiliki kelas yang hanya berisi satu bidang bertipe string, maka salah jika membandingkan equals
bidang ini dengan string dalam suatu metode. Karena dalam kasus perbandingan terbalik, metode akan selalu 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);
}
Dari syarat transitivitas dapat disimpulkan bahwa jika ada dua dari tiga benda yang sama, maka dalam hal ini ketiga benda tersebut harus sama. Prinsip ini dapat dengan mudah dilanggar ketika diperlukan untuk memperluas kelas dasar tertentu dengan menambahkan komponen yang berarti ke dalamnya . Misalnya, ke kelas Point
dengan koordinat x
dan y
Anda perlu menambahkan warna titik dengan memperluasnya. Untuk melakukan ini, Anda perlu mendeklarasikan kelas ColorPoint
dengan bidang yang sesuai color
. Jadi, jika di kelas yang diperluas kita memanggil equals
metode induk, dan di induk kita berasumsi bahwa hanya koordinat x
dan yang dibandingkan y
, maka dua titik dengan warna berbeda tetapi dengan koordinat yang sama akan dianggap sama, dan ini tidak benar. Dalam hal ini perlu diajarkan kelas turunan untuk membedakan warna. Untuk melakukan ini, Anda dapat menggunakan dua metode. Tapi yang satu akan melanggar aturan simetri , dan yang kedua - transitivitas .
// Первый способ, нарушая симметричность
// Метод переопределен в классе ColorPoint
@Override
public boolean equals(Object o) {
if (!(o instanceof ColorPoint)) return false;
return super.equals(o) && ((ColorPoint) o).color == color;
}
Dalam hal ini, panggilan point.equals(colorPoint)
akan mengembalikan nilai true
, dan perbandingan colorPoint.equals(point)
akan mengembalikan false
, karena mengharapkan objek dari kelas "nya". Dengan demikian, aturan simetri dilanggar. Metode kedua melibatkan melakukan pemeriksaan “buta” jika tidak ada data tentang warna titik, yaitu kita memiliki kelas Point
. Atau periksa warnanya jika informasi tentangnya tersedia, yaitu membandingkan 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 transitivitas dilanggar di sini sebagai berikut. Katakanlah ada definisi dari objek berikut:
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
Jadi, meskipun persamaan p1.equals(p2)
dan bernilai true p2.equals(p3)
, p1.equals(p3)
namun akan mengembalikan nilai false
. Sedangkan cara kedua menurut saya kurang menarik karena Dalam beberapa kasus, algoritme mungkin buta dan tidak melakukan perbandingan sepenuhnya, dan Anda mungkin tidak mengetahuinya. Sedikit puisi Secara umum, sepengetahuan saya, tidak ada solusi konkrit untuk masalah ini. Ada pendapat dari salah satu penulis resmi bernama Kay Horstmann bahwa Anda dapat mengganti penggunaan operator instanceof
dengan pemanggilan metode getClass()
yang mengembalikan kelas objek dan, sebelum Anda mulai membandingkan objek itu sendiri, pastikan tipenya sama. , dan tidak memperhatikan fakta asal usulnya yang sama. Dengan demikian, aturan simetri dan transitivitas akan terpenuhi. Namun pada saat yang sama, di sisi lain barikade berdiri penulis lain, yang tidak kalah dihormati di kalangan luas, Joshua Bloch, yang percaya bahwa pendekatan ini melanggar prinsip substitusi Barbara Liskov. Prinsip ini menyatakan bahwa “kode pemanggil harus memperlakukan kelas dasar dengan cara yang sama seperti subkelasnya tanpa menyadarinya . ” Dan dalam solusi yang diajukan Horstmann, prinsip ini jelas dilanggar, karena bergantung pada implementasinya. Singkatnya, jelas bahwa masalahnya gelap. Perlu juga dicatat bahwa Horstmann mengklarifikasi aturan untuk menerapkan pendekatannya dan menulis dalam bahasa Inggris sederhana bahwa Anda perlu memutuskan strategi saat merancang kelas, dan jika pengujian kesetaraan hanya akan dilakukan oleh superclass, Anda dapat melakukannya dengan melakukan operasi instanceof
. Jika tidak, ketika semantik pemeriksaan berubah tergantung pada kelas turunan dan penerapan metode perlu dipindahkan ke bawah hierarki, Anda harus menggunakan metode tersebut getClass()
. Joshua Bloch, pada gilirannya, mengusulkan untuk meninggalkan warisan dan menggunakan komposisi objek dengan memasukkan ColorPoint
kelas ke dalam kelas Point
dan menyediakan metode akses asPoint()
untuk memperoleh informasi khusus tentang titik tersebut. Ini akan menghindari pelanggaran semua aturan, tetapi menurut saya, ini akan membuat kode lebih sulit untuk dipahami. Opsi ketiga adalah menggunakan metode pembuatan persamaan secara otomatis menggunakan IDE. Idenya, omong-omong, mereproduksi generasi Horstmann, memungkinkan Anda memilih strategi untuk mengimplementasikan metode di superclass atau di turunannya. Terakhir, aturan konsistensi berikutnya menyatakan bahwa meskipun objek x
tidak y
berubah, pemanggilan objek lagi x.equals(y)
harus mengembalikan nilai yang sama seperti sebelumnya. Aturan terakhirnya adalah tidak ada objek yang sama dengan null
. Semuanya jelas di sini null
- ini ketidakpastian, apakah objeknya sama dengan ketidakpastian? Tidak jelas, yaitu false
.
Algoritma umum untuk menentukan persamaan
- Periksa kesetaraan referensi objek
this
dan parameter metodeo
.if (this == o) return true;
- Periksa apakah tautan tersebut ditentukan
o
, yaitu apakah sudah ditentukannull
.
Jika di masa depan, saat membandingkan tipe objek, operator akan digunakaninstanceof
, item ini dapat dilewati, karena parameter ini kembalifalse
dalam kasus ininull instanceof Object
. - Bandingkan jenis objek
this
menggunakano
operatorinstanceof
atau metodegetClass()
, dipandu oleh uraian di atas dan intuisi Anda sendiri. - Jika suatu metode
equals
ditimpa dalam subkelas, pastikan untuk melakukan panggilansuper.equals(o)
- Ubah tipe parameter
o
menjadi kelas yang diperlukan. - Lakukan perbandingan semua bidang objek penting:
- untuk tipe primitif (kecuali
float
dandouble
), menggunakan operator==
- untuk bidang referensi Anda perlu memanggil metodenya
equals
- untuk array, Anda dapat menggunakan iterasi siklik atau metodenya
Arrays.equals()
- untuk tipe
float
dandouble
perlu menggunakan metode perbandingan dari kelas pembungkus yang sesuaiFloat.compare()
danDouble.compare()
- untuk tipe primitif (kecuali
- Dan terakhir, jawab tiga pertanyaan: apakah metode yang diterapkan simetris ? Transitif ? Sepakat ? Dua prinsip lainnya ( refleksivitas dan kepastian ) biasanya dijalankan secara otomatis.
Aturan penggantian HashCode
Hash adalah angka yang dihasilkan dari suatu objek yang menggambarkan keadaannya pada suatu titik waktu. Nomor ini digunakan di Java terutama dalam tabel hash sepertiHashMap
. Dalam hal ini, fungsi hash untuk memperoleh angka berdasarkan suatu objek harus diimplementasikan sedemikian rupa untuk memastikan distribusi elemen yang relatif merata di seluruh tabel hash. Dan juga untuk meminimalkan kemungkinan tabrakan ketika fungsi mengembalikan nilai yang sama untuk kunci yang berbeda.
Kode hash kontrak
Untuk mengimplementasikan fungsi hash, spesifikasi bahasa mendefinisikan aturan berikut:- memanggil metode
hashCode
satu kali atau lebih pada objek yang sama harus mengembalikan nilai hash yang sama, asalkan bidang objek yang terlibat dalam penghitungan nilai tidak berubah. - Memanggil metode
hashCode
pada dua objek harus selalu menghasilkan angka yang sama jika objeknya sama (memanggil metodeequals
pada objek ini akan mengembalikantrue
). - memanggil metode
hashCode
pada dua objek yang tidak sama harus menghasilkan nilai hash yang berbeda. Meskipun persyaratan ini tidak wajib, namun harus dipertimbangkan bahwa penerapannya akan berdampak positif pada kinerja tabel hash.
Metode sama dengan dan kode hash harus diganti secara bersamaan
Berdasarkan kontrak yang dijelaskan di atas, maka saat mengganti metode dalam kode Andaequals
, Anda harus selalu mengganti metode tersebut hashCode
. Karena sebenarnya dua instance dari suatu kelas berbeda karena berada di area memori yang berbeda, maka keduanya harus dibandingkan berdasarkan beberapa kriteria logis. Oleh karena itu, dua objek yang setara secara logis harus mengembalikan nilai hash yang sama. Apa yang terjadi jika hanya salah satu dari metode ini yang diganti?
-
equals
yahashCode
TidakKatakanlah kita mendefinisikan metode
equals
di kelas kita dengan benar, danhashCode
memutuskan untuk membiarkan metode tersebut apa adanya di kelasObject
. Maka dari sudut pandang metodeequals
kedua objek tersebut secara logis akan sama, sedangkan dari sudut pandang metodehashCode
keduanya tidak mempunyai kesamaan. Jadi, dengan menempatkan suatu objek dalam tabel hash, kita berisiko tidak mendapatkannya kembali dengan kunci.
Misalnya 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));
Jelasnya, benda yang ditempatkan dan benda yang dicari adalah dua benda yang berbeda, walaupun secara logika keduanya sama. Tapi karena mereka memiliki nilai hash yang berbeda karena kita melanggar kontrak, kita dapat mengatakan bahwa kita kehilangan objek kita di suatu tempat di dalam tabel hash.
-
hashCode
yaequals
Tidak.Apa yang terjadi jika kita mengganti metode
hashCode
danequals
mewarisi implementasi metode dari kelasObject
. Seperti yang Anda ketahui,equals
metode default hanya membandingkan pointer ke objek, menentukan apakah pointer tersebut merujuk ke objek yang sama. Mari kita asumsikan bahwahashCode
kita telah menulis metode sesuai dengan semua kanon, yaitu, menghasilkannya menggunakan IDE, dan metode tersebut akan mengembalikan nilai hash yang sama untuk objek yang identik secara logis. Jelasnya, dengan melakukan ini kita telah mendefinisikan beberapa mekanisme untuk membandingkan dua objek.Oleh karena itu, contoh dari paragraf sebelumnya secara teori harus dilaksanakan. Namun kami tetap tidak dapat menemukan objek kami di tabel hash. Meskipun kita akan mendekati ini, karena minimal kita akan menemukan keranjang tabel hash di mana objek tersebut akan berada.
Agar berhasil mencari suatu objek dalam tabel hash, selain membandingkan nilai hash kunci, juga digunakan penentuan persamaan logika kunci dengan objek yang dicari. Artinya,
equals
tidak ada cara untuk melakukannya tanpa mengesampingkan metode tersebut.
Algoritma umum untuk menentukan kode hash
Di sini, menurut saya, Anda tidak perlu terlalu khawatir dan membuat metode di IDE favorit Anda. Karena semua perpindahan bit ke kanan dan kiri untuk mencari rasio emas, yaitu distribusi normal, adalah untuk pria yang benar-benar keras kepala. Secara pribadi, saya ragu apakah saya bisa melakukan lebih baik dan lebih cepat daripada Ide yang sama.Alih-alih sebuah kesimpulan
Jadi, kita melihat bahwa metodeequals
memainkan hashCode
peran yang jelas dalam bahasa Java dan dirancang untuk memperoleh karakteristik persamaan logis dari dua objek. Dalam kasus metode, equals
ini memiliki hubungan langsung dengan membandingkan objek, dalam kasus hashCode
tidak langsung, ketika diperlukan, katakanlah, untuk menentukan perkiraan lokasi suatu objek dalam tabel hash atau struktur data serupa untuk meningkatkan kecepatan mencari suatu objek. Selain akad , equals
ada hashCode
syarat lain yang berkaitan dengan perbandingan benda. Ini adalah konsistensi metode compareTo
antarmuka Comparable
dengan file equals
. Persyaratan ini mewajibkan pengembang untuk selalu kembali x.equals(y) == true
kapan x.compareTo(y) == 0
. Artinya, kita melihat bahwa perbandingan logis dari dua objek tidak boleh bertentangan dimanapun dalam penerapannya dan harus selalu konsisten.
GO TO FULL VERSION