Zastąpienie metody równa się java () - nie działa?

150

equals()Dzisiaj napotkałem interesujący (i bardzo frustrujący) problem z tą metodą, który spowodował awarię klasy, którą uważałem za dobrze przetestowaną, i spowodowanie błędu, którego wytropienie zajęło mi bardzo dużo czasu.

Dla kompletności nie używałem IDE ani debuggera - tylko dobry, staroświecki edytor tekstu i System.out. Czas był bardzo ograniczony i był to projekt szkolny.

W każdym razie -

Ja rozwijał podstawowy koszyk, które mogą zawierać ArrayListod Bookobiektów . W celu wprowadzenia w życie addBook(), removeBook()oraz hasBook()metod Koszyka, chciałem sprawdzić, czy Bookistniała już w Cart. Więc idę -

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

W testowaniu wszystko działa dobrze. Tworzę 6 obiektów i wypełniam je danymi. Czy wiele dodaje, usuwa, ma operacje () na Carti wszystko działa dobrze. Czytałem, że możesz albo mieć, equals(TYPE var)alboequals(Object o) { (CAST) var } założyć, że skoro to działa, nie ma to większego znaczenia.

Potem napotkałem problem - musiałem stworzyć Bookobiekt zawierający tylko ten obiekt z IDklasy Book. Żadne inne dane nie zostałyby do niego wprowadzone. Zasadniczo następujące:

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);
}

Nagle equals(Book b)metoda przestaje działać. To zajęło BARDZO dużo czasu, aby wyśledzić bez dobrego debuggera i przy założeniu, że Cartklasa została odpowiednio przetestowana i poprawna. Po przejściu equals()metody do następującego:

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

Wszystko znów zaczęło działać. Czy istnieje powód, dla którego metoda zdecydowała się nie przyjmować parametru Book, mimo że najwyraźniej był to Bookobiekt? Jedyna różnica polegała na tym, że został utworzony z tej samej klasy i wypełniony tylko jednym składnikiem danych. Jestem bardzo zdezorientowany. Proszę, rzuć trochę światła?

Josh Smeaton
źródło
1
Zdaję sobie sprawę, że naruszyłem `` Kontrakt '' dotyczący zastępowania metod equals poprzez refleksję - jednak potrzebowałem szybkiego sposobu sprawdzenia, czy obiekt istnieje w ArrayList bez użycia typów ogólnych.
Josh Smeaton
1
To dobra lekcja do nauki o Javie i równych sobie
jjnguy

Odpowiedzi:

329

W Javie equals()dziedziczona jest metoda Object:

public boolean equals(Object other);

Innymi słowy, parametr musi być typu Object. Nazywa się to nadpisywaniem ; metoda public boolean equals(Book other)robi to, co nazywa się przeciążenia do equals()metody.

Do porównywania zawartości (np. Metod i ) ArrayListużywa equals()metod nadpisanych , a nie przeciążonych. W większości twojego kodu wywołanie tego, który nie przesłania poprawnie równości, było w porządku, ale nie było zgodne z .contains()equals()ObjectArrayList

Dlatego niepoprawne zastąpienie metody może spowodować problemy.

Za każdym razem nadpisuję:

@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...
}

Użycie @Overrideadnotacji może pomóc tonie w głupich błędach.

Użyj go, gdy myślisz, że nadpisujesz metodę superklasy lub interfejsu. W ten sposób, jeśli zrobisz to w niewłaściwy sposób, otrzymasz błąd kompilacji.

jjnguy
źródło
31
To dobry argument przemawiający za adnotacją @Override ... gdyby OP używał @Override, jego kompilator powiedziałby mu, że tak naprawdę nie przesłania metody klasy nadrzędnej ...
Cowan
1
Nigdy nie wiedziałem o @Override, dzięki za to! Chciałbym również dodać, że przesłanianie hashCode () naprawdę powinno było zostać zrobione i mogło wcześniej wykryć błąd.
Josh Smeaton
5
Niektóre środowiska IDE (np. Eclipse) mogą nawet automatycznie generować metody equals () i hashcode () na podstawie zmiennych składowych klasy.
sk.
1
if (!(other instanceof MyClass))return false;zwraca, falsejeśli MyClassrozszerza inną klasę. Ale nie powróci, falsejeśli druga klasa się rozszerzy MyClass. Nie powinno equalbyć mniej sprzeczne?
Robert,
19
W przypadku korzystania z wystąpienia poprzedniego nullcheck jest zbędne.
Mateusz Dymczyk
108

Jeśli używasz zaćmienia, po prostu przejdź do górnego menu

Source -> Generate equals () and hashCode ()

Fred
źródło
Zgadzam się! Ten, o którym nigdy wcześniej nie wiedziałem, a jego wygenerowanie sprawia, że ​​jest mniej podatny na błędy
Boy,
To samo tutaj. Dzięki Fred!
Anila
16
W IntelliJ znajdziesz to w Kod → Generuj… lub control + N. :)
rightfold
W Netbeans przejdź do paska menu> Źródło (lub kliknij prawym przyciskiem myszy)> Wstaw kod (lub Ctrl-I) i kliknij Generuj równa się () ...
Salomon
11

Trochę nie na temat twojego pytania, ale i tak prawdopodobnie warto o tym wspomnieć:

Commons Lang ma kilka doskonałych metod, których możesz użyć do nadpisywania równości i hashcode. Sprawdź EqualsBuilder.reflectionEquals (...) i HashCodeBuilder.reflectionHashCode (...) . W przeszłości zaoszczędziło mi to wielu bólu głowy - chociaż oczywiście, jeśli chcesz po prostu zrobić „równa się” na legitymacji, może to nie pasować do twoich okoliczności.

Zgadzam się również, że powinieneś używać @Overrideadnotacji zawsze, gdy zastępujesz równa się (lub jakakolwiek inna metoda).


źródło
4
Jeśli jesteś użytkownikiem zaćmienia, możesz również iść right click -> source -> generate hashCode() and equals(),
tunaranch
1
Czy mam rację, że ta metoda jest wykonywana w czasie wykonywania? Czy nie będziemy mieli problemów z wydajnością na wypadek, gdybyśmy przemierzali dużą kolekcję z przedmiotami sprawdzającymi je pod względem równości z jakimś innym przedmiotem z powodu refleksji?
Gaket
4

Innym szybkim rozwiązaniem, które zapisuje standardowy kod, jest adnotacja Lombok EqualsAndHashCode . Jest łatwy, elegancki i konfigurowalny. I nie zależy od IDE . Na przykład;

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;

Zobacz dostępne opcje, aby dostosować, które pola mają być używane w równych. Lombok jest dostępny w Maven . Po prostu dodaj go z podanym zakresem:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.14.8</version>
    <scope>provided</scope>
</dependency>
borjab
źródło
1

w Android Studio jest alt + insert ---> equals i hashCode

Przykład:

    @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
źródło
1

Rozważać:

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
źródło
1
@Elazar Jak to? objjest zadeklarowany jako Object. Punktem dziedziczenia jest to, że możesz następnie przypisać Bookdo obj. Po tym, jeśli nie zasugerujesz, że a Objectnie powinno być porównywalne z Stringvia equals(), kod ten powinien być całkowicie legalny i zwracać false.
bcsb1001
Sugeruję dokładnie to. Uważam, że jest to dość powszechnie akceptowane.
Elazar
0

instanceOfoświadczenie jest często używany w realizacji równymi.

To popularna pułapka!

Problem w tym, że używanie instanceOfnarusza zasadę symetrii:

(object1.equals(object2) == true) wtedy i tylko wtedy gdy (object2.equals(object1))

jeśli pierwsze równa się jest prawdziwe, a obiekt2 jest instancją podklasy klasy, do której należy obiekt1, to drugie równa się zwróci fałsz!

jeśli rozpatrywana klasa, do której należy ob1, jest zadeklarowana jako ostateczna, to ten problem nie może powstać, ale generalnie należy przetestować w następujący sposób:

this.getClass() != otherObject.getClass(); jeśli nie, zwróć false, w przeciwnym razie przetestuj pola, aby porównać je pod kątem równości!

Nikel8000
źródło
3
Zobacz Bloch, Effective Java, pozycja 8, obszerna sekcja omawiająca problemy z przesłonięciem equals()metody. Odradza używanie getClass(). Głównym powodem jest to, że takie postępowanie łamie zasadę zastępowania Liskova dla podklas, które nie wpływają na równość.
Stuart Marks
-1

recordId jest właściwością obiektu

@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
źródło