Mengganti metode java equals () - tidak berfungsi?

150

Saya mengalami masalah yang menarik (dan sangat membuat frustrasi) dengan equals()metode hari ini yang menyebabkan apa yang saya pikir sebagai kelas yang teruji rusak dan menyebabkan bug yang membutuhkan waktu sangat lama untuk dilacak.

Hanya untuk kelengkapan, saya tidak menggunakan IDE atau debugger - hanya editor teks kuno dan System.out yang bagus. Waktu sangat terbatas dan itu adalah proyek sekolah.

Bagaimanapun -

Saya sedang mengembangkan keranjang belanja dasar yang bisa mengandung ArrayListdari Bookobjek . Dalam rangka mengimplementasikan addBook(), removeBook()dan hasBook()metode Cart, saya ingin memeriksa apakah Booksudah ada di Cart. Jadi saya pergi -

public boolean equals(Book b) {
    ... // More code here - null checks
    if (b.getID() == this.getID()) return true;
    else return false;
}

Semua berfungsi dengan baik dalam pengujian. Saya membuat 6 objek dan mengisinya dengan data. Apakah banyak menambahkan, menghapus, memiliki () operasi di Cartdan semuanya berfungsi dengan baik. Saya membaca bahwa Anda dapat memiliki equals(TYPE var)atauequals(Object o) { (CAST) var } tetapi berasumsi bahwa karena itu berfungsi, itu tidak terlalu penting.

Lalu aku berlari ke masalah - saya butuhkan untuk membuat Bookobjek dengan hanya satu IDdi dalamnya dari dalam kelas Book. Tidak ada data lain yang akan dimasukkan ke dalamnya. Pada dasarnya sebagai berikut:

public boolean hasBook(int i) {
    Book b = new Book(i);
    return hasBook(b);
}

public boolean hasBook(Book b) {
    // .. more code here
    return this.books.contains(b);
}

Tiba-tiba, equals(Book b)metode ini tidak lagi berfungsi. Ini membutuhkan waktu yang SANGAT lama untuk dilacak tanpa debugger yang bagus dan menganggap Cartkelas telah diuji dengan benar dan benar. Setelah menunggu equals()metode sebagai berikut:

public boolean equals(Object o) {
    Book b = (Book) o;
    ... // The rest goes here   
}

Segalanya mulai bekerja lagi. Apakah ada alasan metode memutuskan untuk tidak mengambil parameter Book meskipun itu jelas adalah sebuah Bookobjek? Satu-satunya perbedaan tampaknya adalah itu dipakai dari dalam kelas yang sama, dan hanya diisi dengan satu anggota data. Saya sangat sangat bingung. Tolong, beri sedikit cahaya?

Josh Smeaton
sumber
1
Saya sadar bahwa saya melanggar 'Kontrak' tentang mengganti metode equals dengan menjadi reflektif - namun saya perlu cara cepat untuk memeriksa apakah objek ada di ArrayList tanpa menggunakan obat generik.
Josh Smeaton
1
Ini adalah pelajaran yang baik untuk belajar tentang Jawa dan sederajat
jjnguy

Jawaban:

329

Di Jawa, equals()metode yang diwarisi dari Objectadalah:

public boolean equals(Object other);

Dengan kata lain, parameter harus bertipe Object. Ini disebut overriding ; metode Anda public boolean equals(Book other)melakukan apa yang disebut overloading ke equals()metode tersebut.

The ArrayListpenggunaan ditimpa equals()metode untuk membandingkan isi (misalnya untuk yang contains()dan equals()metode), bukan yang kelebihan beban. Dalam sebagian besar kode Anda, menyebut salah satu yang tidak benar menimpa Objectequals 's baik-baik saja, tapi tidak kompatibel dengan ArrayList.

Jadi, tidak mengganti metode dengan benar dapat menyebabkan masalah.

Saya menimpa sama dengan setiap kali berikut:

@Override
public boolean equals(Object other){
    if (other == null) return false;
    if (other == this) return true;
    if (!(other instanceof MyClass)) return false;
    MyClass otherMyClass = (MyClass)other;
    ...test other properties here...
}

Penggunaan @Overrideanotasi dapat membantu satu ton dengan kesalahan konyol.

Gunakan setiap kali Anda berpikir Anda menimpa metode kelas super atau antarmuka. Dengan begitu, jika Anda melakukannya dengan cara yang salah, Anda akan mendapatkan kesalahan kompilasi.

jjnguy
sumber
31
Ini adalah argumen yang bagus untuk mendukung anotasi @Override ... jika OP menggunakan @Override kompilernya akan memberitahunya bahwa dia tidak benar-benar mengganti metode kelas induk ...
Cowan
1
Tidak pernah menyadari @Override, terima kasih untuk itu! Saya juga ingin menambahkan bahwa hashCode () yang benar-benar seharusnya telah dilakukan dan mungkin telah melihat kesalahan lebih cepat.
Josh Smeaton
5
Beberapa IDE (misalnya Eclipse) bahkan dapat membuat autogenerate equals () dan hashcode () metode untuk Anda berdasarkan variabel anggota kelas.
sk.
1
if (!(other instanceof MyClass))return false;kembali falsejika MyClassmemperluas kelas lain. Tapi itu tidak akan kembali falsejika kelas lainnya diperpanjang MyClass. Bukankah seharusnya tidak equalterlalu kontradiktif?
Robert
19
Saat menggunakan instanceof, nullcheck sebelumnya adalah redundan.
Mateusz Dymczyk
108

Jika Anda menggunakan gerhana hanya pergi ke menu atas

Sumber -> Hasilkan equals () dan hashCode ()

Fred
sumber
Saya setuju! Yang ini saya tidak pernah tahu sebelumnya dan membuatnya kurang rawan kesalahan
Boy
Sama disini. Fred terima kasih!
Anila
16
Di IntelliJ Anda menemukan ini di bawah Kode → Hasilkan ... atau kontrol + N. :)
rightfold
Di Netbeans Anda pergi ke bilah menu> Sumber (atau klik kanan)> Sisipkan Kode (atau Ctrl-I), dan klik Hasilkan sama dengan () ...
Solomon
11

Sedikit di luar topik untuk pertanyaan Anda, tetapi mungkin perlu disebutkan:

Commons Lang telah mendapatkan beberapa metode luar biasa yang dapat Anda gunakan dalam mengesampingkan equals dan hashcode. Lihat EqualsBuilder.reflectionEquals (...) dan HashCodeBuilder.reflectionHashCode (...) . Menyelamatkan saya banyak sakit kepala di masa lalu - walaupun tentu saja jika Anda hanya ingin melakukan "sama dengan" pada ID itu mungkin tidak sesuai dengan keadaan Anda.

Saya juga setuju bahwa Anda harus menggunakan @Overrideanotasi setiap kali Anda mengganti sama dengan (atau metode lainnya).


sumber
4
Jika Anda pengguna gerhana, Anda juga dapat pergi right click -> source -> generate hashCode() and equals(),
tunaranch
1
Apakah saya benar bahwa metode ini dijalankan pada saat runtime? Tidakkah kita akan memiliki masalah kinerja jika kita menelusuri koleksi besar dengan item memeriksa mereka untuk kesetaraan ke beberapa item lain karena refleksi?
Gaket
4

Solusi cepat lain yang menyimpan kode boilerplate adalah anotasi Lombok EqualsAndHashCode . Mudah, elegan, dan dapat disesuaikan. Dan tidak tergantung pada IDE . Sebagai contoh;

import lombok.EqualsAndHashCode;

@EqualsAndHashCode(of={"errorNumber","messageCode"}) // Will only use this fields to generate equals.
public class ErrorMessage{

    private long        errorNumber;
    private int         numberOfParameters;
    private Level       loggingLevel;
    private String      messageCode;

Lihat opsi yang tersedia untuk menyesuaikan bidang mana yang akan digunakan dalam persamaan. Lombok tersedia di pakar . Cukup tambahkan dengan cakupan yang disediakan :

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.14.8</version>
    <scope>provided</scope>
</dependency>
borjab
sumber
1

di Android Studio alt + masukkan ---> sama dengan dan kode hash

Contoh:

    @Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Proveedor proveedor = (Proveedor) o;

    return getId() == proveedor.getId();

}

@Override
public int hashCode() {
    return getId();
}
David Hackro
sumber
1

Mempertimbangkan:

Object obj = new Book();
obj.equals("hi");
// Oh noes! What happens now? Can't call it with a String that isn't a Book...
bcsb1001
sumber
1
@ Elazar Bagaimana? objdinyatakan sebagai Object. Titik warisan adalah bahwa Anda kemudian dapat menetapkan Bookuntuk obj. Setelah itu, kecuali Anda menyarankan agar Objecttidak boleh dibandingkan dengan Stringvia equals(), kode ini harus sah dan dikembalikan false.
bcsb1001
Saya menyarankan hal itu. Saya percaya ini diterima secara luas.
Elazar
0

yang instanceOfpernyataan sering digunakan dalam pelaksanaan equals.

Ini adalah perangkap yang populer!

Masalahnya adalah bahwa menggunakan instanceOfmelanggar aturan simetri:

(object1.equals(object2) == true) jika dan hanya jika (object2.equals(object1))

jika equals pertama benar, dan object2 adalah turunan dari subclass dari kelas di mana obj1 milik, maka equals kedua akan kembali salah!

jika kelas dianggap di mana ob1 milik dinyatakan sebagai final, maka masalah ini tidak dapat muncul, tetapi secara umum, Anda harus menguji sebagai berikut:

this.getClass() != otherObject.getClass(); jika tidak, kembalikan salah, jika tidak, uji bidang untuk membandingkan kesetaraan!

Nikel8000
sumber
3
Lihat Bloch, Java Efektif, Butir 8, bagian besar yang membahas masalah dengan mengganti equals()metode. Dia merekomendasikan untuk tidak menggunakan getClass(). Alasan utama adalah bahwa hal itu melanggar Prinsip Substitusi Liskov untuk subclass yang tidak mempengaruhi kesetaraan.
Stuart Marks
-1

recordId adalah properti dari objek

@Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Nai_record other = (Nai_record) obj;
        if (recordId == null) {
            if (other.recordId != null)
                return false;
        } else if (!recordId.equals(other.recordId))
            return false;
        return true;
    }
vootla561
sumber