Masalah / perangkap apa yang harus dipertimbangkan ketika mengesampingkan equals
dan hashCode
?
Masalah / perangkap apa yang harus dipertimbangkan ketika mengesampingkan equals
dan hashCode
?
Jawaban:
equals()
( javadoc ) harus mendefinisikan hubungan ekivalensi (harus refleksif , simetris , dan transitif ). Selain itu, harus konsisten (jika objek tidak dimodifikasi, maka ia harus tetap mengembalikan nilai yang sama). Selanjutnya, o.equals(null)
harus selalu mengembalikan false.
hashCode()
( javadoc ) juga harus konsisten (jika objek tidak dimodifikasi dalam hal equals()
, harus tetap mengembalikan nilai yang sama).
The hubungan antara dua metode adalah:
Kapanpun
a.equals(b)
, makaa.hashCode()
harus sama denganb.hashCode()
.
Jika Anda menimpa satu, maka Anda harus menimpa yang lain.
Gunakan kumpulan bidang yang sama yang Anda gunakan untuk menghitung equals()
untuk menghitung hashCode()
.
Gunakan kelas pembantu yang sangat baik, EqualsBuilder dan HashCodeBuilder dari perpustakaan Apache Commons Lang . Sebuah contoh:
public class Person {
private String name;
private int age;
// ...
@Override
public int hashCode() {
return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers
// if deriving: appendSuper(super.hashCode()).
append(name).
append(age).
toHashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person))
return false;
if (obj == this)
return true;
Person rhs = (Person) obj;
return new EqualsBuilder().
// if deriving: appendSuper(super.equals(obj)).
append(name, rhs.name).
append(age, rhs.age).
isEquals();
}
}
Saat menggunakan Koleksi atau Peta berbasis hash seperti HashSet , LinkedHashSet , HashMap , Hashtable , atau WeakHashMap , pastikan kode hash () dari objek kunci yang Anda masukkan ke dalam koleksi tidak pernah berubah saat objek berada di koleksi. Cara anti peluru untuk memastikan ini adalah membuat kunci Anda tidak berubah, yang juga memiliki manfaat lain .
instanceof
mengembalikan false jika operan pertamanya nol (Java lagi efektif)
Ada beberapa masalah yang perlu diperhatikan jika Anda berurusan dengan kelas yang bertahan menggunakan Object-Relationship Mapper (ORM) seperti Hibernate, jika Anda tidak berpikir ini sudah terlalu rumit rumit!
Objek yang dimuat malas adalah subclass
Jika objek Anda tetap menggunakan ORM, dalam banyak kasus Anda akan berhadapan dengan proxy dinamis untuk menghindari memuat objek terlalu dini dari penyimpanan data. Proxy ini diimplementasikan sebagai subclass dari kelas Anda sendiri. Ini berarti this.getClass() == o.getClass()
akan kembali false
. Sebagai contoh:
Person saved = new Person("John Doe");
Long key = dao.save(saved);
dao.flush();
Person retrieved = dao.retrieve(key);
saved.getClass().equals(retrieved.getClass()); // Will return false if Person is loaded lazy
Jika Anda berurusan dengan ORM, gunakan o instanceof Person
adalah satu-satunya hal yang akan berperilaku benar.
Objek malas yang dimuat memiliki bidang-nol
ORM biasanya menggunakan getter untuk memaksa pemuatan objek bermuatan malas. Ini berarti bahwa person.name
akan null
jika person
dimuat malas, bahkan jika person.getName()
memaksa memuat dan mengembalikan "John Doe". Dalam pengalaman saya, ini muncul lebih sering di hashCode()
danequals()
.
Jika Anda berurusan dengan ORM, pastikan untuk selalu menggunakan getter, dan jangan pernah referensi bidang di hashCode()
dan equals()
.
Menyimpan objek akan mengubah kondisinya
Objek persisten sering menggunakan id
bidang untuk menahan kunci objek. Bidang ini akan diperbarui secara otomatis saat sebuah objek disimpan pertama kali. Jangan gunakan bidang id di hashCode()
. Tapi Anda bisa menggunakannya equals()
.
Pola yang sering saya gunakan adalah
if (this.getId() == null) {
return this == other;
}
else {
return this.getId().equals(other.getId());
}
Tetapi: Anda tidak dapat menyertakan getId()
di hashCode()
. Jika Anda melakukannya, ketika suatu objek bertahan, ituhashCode
perubahannya. Jika objek berada dalam a HashSet
, Anda "tidak akan" menemukannya lagi.
Dalam Person
contoh saya , saya mungkin akan menggunakan getName()
untuk hashCode
dan getId()
plus getName()
(hanya untuk paranoia) untuk equals()
. Tidak apa-apa jika ada beberapa risiko "tabrakan" untuk hashCode()
, tetapi tidak pernah baik untuk itu equals()
.
hashCode()
harus menggunakan subset properti yang tidak berubah dari equals()
Saving an object will change it's state
! hashCode
harus kembali int
, jadi bagaimana Anda akan menggunakan getName()
? Bisakah Anda memberi contoh untuk AndahashCode
Klarifikasi tentang obj.getClass() != getClass()
.
Pernyataan ini adalah hasil dari equals()
pewarisan yang tidak ramah. JLS (spesifikasi bahasa Java) menetapkan bahwa jika A.equals(B) == true
kemudian B.equals(A)
harus juga kembali true
. Jika Anda mengabaikan pernyataan yang mewarisi kelas yang menimpa equals()
(dan mengubah perilakunya) akan melanggar spesifikasi ini.
Pertimbangkan contoh berikut tentang apa yang terjadi ketika pernyataan dihilangkan:
class A {
int field1;
A(int field1) {
this.field1 = field1;
}
public boolean equals(Object other) {
return (other != null && other instanceof A && ((A) other).field1 == field1);
}
}
class B extends A {
int field2;
B(int field1, int field2) {
super(field1);
this.field2 = field2;
}
public boolean equals(Object other) {
return (other != null && other instanceof B && ((B)other).field2 == field2 && super.equals(other));
}
}
Melakukan new A(1).equals(new A(1))
Juga, new B(1,1).equals(new B(1,1))
hasil memberi yang benar, sebagaimana mestinya.
Ini terlihat sangat bagus, tetapi lihat apa yang terjadi jika kita mencoba menggunakan kedua kelas:
A a = new A(1);
B b = new B(1,1);
a.equals(b) == true;
b.equals(a) == false;
Jelas, ini salah.
Jika Anda ingin memastikan kondisi simetris. a = b jika b = a dan panggilan prinsip substitusi Liskov super.equals(other)
tidak hanya dalam hal B
instance, tetapi periksa juga A
misalnya:
if (other instanceof B )
return (other != null && ((B)other).field2 == field2 && super.equals(other));
if (other instanceof A) return super.equals(other);
else return false;
Yang akan menghasilkan:
a.equals(b) == true;
b.equals(a) == true;
Di mana, jika a
bukan referensi B
, maka itu mungkin menjadi referensi kelas A
(karena Anda memperluasnya), dalam hal ini Anda menelepon super.equals()
juga .
ThingWithOptionSetA
dapat sama dengan Thing
asalkan semua opsi tambahan memiliki nilai default, dan juga untuk a ThingWithOptionSetB
, maka harus mungkin untuk ThingWithOptionSetA
dibandingkan dengan ThingWithOptionSetB
hanya jika semua properti non-basis dari kedua objek cocok dengan default mereka, tetapi Saya tidak melihat bagaimana Anda menguji itu.
B b2 = new B(1,99)
, lalu b.equals(a) == true
dan a.equals(b2) == true
tetapi b.equals(b2) == false
.
Untuk implementasi yang ramah-warisan, lihat solusi Tal Cohen, Bagaimana Saya Menerapkan Metode equals () dengan benar?
Ringkasan:
Dalam bukunya Panduan Bahasa Pemrograman Java yang Efektif (Addison-Wesley, 2001), Joshua Bloch mengklaim bahwa "Tidak ada cara untuk memperluas kelas yang mungkin ada dan menambahkan aspek sambil mempertahankan kontrak yang sama." Tal tidak setuju.
Solusinya adalah mengimplementasikan equals () dengan memanggil blindlyEquals nonsimetrik lain () keduanya. blindlyEquals () ditimpa oleh subclass, equals () diwarisi, dan tidak pernah diganti.
Contoh:
class Point {
private int x;
private int y;
protected boolean blindlyEquals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point)o;
return (p.x == this.x && p.y == this.y);
}
public boolean equals(Object o) {
return (this.blindlyEquals(o) && o.blindlyEquals(this));
}
}
class ColorPoint extends Point {
private Color c;
protected boolean blindlyEquals(Object o) {
if (!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint)o;
return (super.blindlyEquals(cp) &&
cp.color == this.color);
}
}
Perhatikan bahwa equals () harus bekerja lintas hierarki warisan jika Prinsip Substitusi Liskov harus dipenuhi.
if (this.getClass() != o.getClass()) return false
, tetapi fleksibel karena hanya mengembalikan false jika kelas turunannya repot untuk memodifikasi sama. Apakah itu benar?
Masih heran bahwa tidak ada yang merekomendasikan perpustakaan jambu biji untuk ini.
//Sample taken from a current working project of mine just to illustrate the idea
@Override
public int hashCode(){
return Objects.hashCode(this.getDate(), this.datePattern);
}
@Override
public boolean equals(Object obj){
if ( ! obj instanceof DateAndPattern ) {
return false;
}
return Objects.equal(((DateAndPattern)obj).getDate(), this.getDate())
&& Objects.equal(((DateAndPattern)obj).getDate(), this.getDatePattern());
}
this
di this.getDate()
berarti apa-apa (selain kekacauan)
if (!(otherObject instanceof DateAndPattern)) {
. Setuju dengan hernan dan Steve Kuo (meskipun itu masalah pilihan pribadi), tapi tetap saja +1.
Ada dua metode dalam kelas super sebagai java.lang.Object. Kita perlu menimpanya ke objek kustom.
public boolean equals(Object obj)
public int hashCode()
Objek yang sama harus menghasilkan kode hash yang sama selama mereka sama, namun objek yang tidak sama tidak perlu menghasilkan kode hash yang berbeda.
public class Test
{
private int num;
private String data;
public boolean equals(Object obj)
{
if(this == obj)
return true;
if((obj == null) || (obj.getClass() != this.getClass()))
return false;
// object must be Test at this point
Test test = (Test)obj;
return num == test.num &&
(data == test.data || (data != null && data.equals(test.data)));
}
public int hashCode()
{
int hash = 7;
hash = 31 * hash + num;
hash = 31 * hash + (null == data ? 0 : data.hashCode());
return hash;
}
// other methods
}
Jika Anda ingin mendapatkan lebih banyak, silakan periksa tautan ini sebagai http://www.javaranch.com/journal/2002/10/equalhash.html
Ini adalah contoh lain, http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html
Selamat bersenang-senang! @. @
Ada beberapa cara untuk melakukan pemeriksaan kesetaraan kelas sebelum memeriksa kesetaraan anggota, dan saya pikir keduanya berguna dalam keadaan yang tepat.
instanceof
operator.this.getClass().equals(that.getClass())
.Saya menggunakan # 1 dalam final
implementasi equals, atau ketika mengimplementasikan antarmuka yang menentukan algoritma untuk equals (seperti java.util
antarmuka koleksi — cara yang tepat untuk memeriksa dengan(obj instanceof Set)
atau antarmuka apa pun yang Anda laksanakan). Ini umumnya merupakan pilihan yang buruk ketika persamaan dapat ditimpa karena itu merusak properti simetri.
Opsi # 2 memungkinkan kelas untuk diperpanjang dengan aman tanpa mengesampingkan sama atau melanggar simetri.
Jika kelas Anda juga Comparable
, metode equals
dan compareTo
harus konsisten juga. Inilah templat untuk metode sama dengan di Comparable
kelas:
final class MyClass implements Comparable<MyClass>
{
…
@Override
public boolean equals(Object obj)
{
/* If compareTo and equals aren't final, we should check with getClass instead. */
if (!(obj instanceof MyClass))
return false;
return compareTo((MyClass) obj) == 0;
}
}
final
, dan compareTo()
metodenya diganti untuk membalik urutan, contoh dari subclass dan superclass tidak boleh dianggap sama. Ketika objek-objek ini digunakan bersama-sama di pohon, kunci yang "sama" sesuai dengan instanceof
implementasi mungkin tidak dapat ditemukan.
Untuk yang sederajat, lihatlah Rahasia dari Persamaan oleh Angelika Langer . Saya sangat menyukainya. Dia juga FAQ yang bagus tentang Generik di Jawa . Lihat artikel lainnya di sini (gulir ke bawah ke "Core Java"), di mana ia juga melanjutkan dengan Bagian-2 dan "perbandingan tipe campuran". Selamat membaca!
metode equals () digunakan untuk menentukan kesetaraan dua objek.
karena nilai int 10 selalu sama dengan 10. Tapi metode equals () ini adalah tentang persamaan dua objek. Ketika kita mengatakan objek, ia akan memiliki properti. Untuk memutuskan kesetaraan sifat-sifat tersebut dipertimbangkan. Tidak perlu bahwa semua properti harus diperhitungkan untuk menentukan kesetaraan dan sehubungan dengan definisi dan konteks kelas itu dapat diputuskan. Kemudian metode equals () dapat diganti.
kita harus selalu mengganti metode hashCode () setiap kali kita mengganti metode equals (). Jika tidak, apa yang akan terjadi? Jika kita menggunakan hashtable dalam aplikasi kita, itu tidak akan berlaku seperti yang diharapkan. Karena kode hash digunakan dalam menentukan kesetaraan nilai yang disimpan, kode hash tidak akan mengembalikan nilai terkait yang tepat untuk kunci.
Implementasi default yang diberikan adalah metode hashCode () di kelas Object menggunakan alamat internal objek dan mengubahnya menjadi integer dan mengembalikannya.
public class Tiger {
private String color;
private String stripePattern;
private int height;
@Override
public boolean equals(Object object) {
boolean result = false;
if (object == null || object.getClass() != getClass()) {
result = false;
} else {
Tiger tiger = (Tiger) object;
if (this.color == tiger.getColor()
&& this.stripePattern == tiger.getStripePattern()) {
result = true;
}
}
return result;
}
// just omitted null checks
@Override
public int hashCode() {
int hash = 3;
hash = 7 * hash + this.color.hashCode();
hash = 7 * hash + this.stripePattern.hashCode();
return hash;
}
public static void main(String args[]) {
Tiger bengalTiger1 = new Tiger("Yellow", "Dense", 3);
Tiger bengalTiger2 = new Tiger("Yellow", "Dense", 2);
Tiger siberianTiger = new Tiger("White", "Sparse", 4);
System.out.println("bengalTiger1 and bengalTiger2: "
+ bengalTiger1.equals(bengalTiger2));
System.out.println("bengalTiger1 and siberianTiger: "
+ bengalTiger1.equals(siberianTiger));
System.out.println("bengalTiger1 hashCode: " + bengalTiger1.hashCode());
System.out.println("bengalTiger2 hashCode: " + bengalTiger2.hashCode());
System.out.println("siberianTiger hashCode: "
+ siberianTiger.hashCode());
}
public String getColor() {
return color;
}
public String getStripePattern() {
return stripePattern;
}
public Tiger(String color, String stripePattern, int height) {
this.color = color;
this.stripePattern = stripePattern;
this.height = height;
}
}
Output Kode Contoh:
bengalTiger1 and bengalTiger2: true
bengalTiger1 and siberianTiger: false
bengalTiger1 hashCode: 1398212510
bengalTiger2 hashCode: 1398212510
siberianTiger hashCode: –1227465966
Satu gotcha yang saya temukan adalah di mana dua objek berisi referensi satu sama lain (satu contoh menjadi hubungan orang tua / anak dengan metode kenyamanan pada orang tua untuk mendapatkan semua anak).
Hal-hal semacam ini cukup umum ketika melakukan pemetaan Hibernate misalnya.
Jika Anda memasukkan kedua ujung hubungan dalam kode hash Anda atau sama dengan tes itu mungkin untuk masuk ke loop rekursif yang berakhir dengan StackOverflowException.
Solusi paling sederhana adalah tidak memasukkan koleksi getChildren dalam metode.
equals()
. Jika seorang ilmuwan gila membuat duplikat saya, kami akan setara. Tetapi kita tidak akan memiliki ayah yang sama.