Sostituire il metodo java equals () - non funziona?


150

Mi sono imbattuto in un problema interessante (e molto frustrante) con il equals()metodo oggi che ha causato l'arresto anomalo di quella che pensavo fosse una classe ben collaudata e causava un errore che mi ha impiegato molto tempo a rintracciarlo.

Solo per completezza, non stavo usando un IDE o un debugger, solo un buon editor di testo vecchio stile e System.out. Il tempo era molto limitato ed era un progetto scolastico.

Comunque -

Stavo sviluppando un carrello di base che potrebbe contenere un ArrayListdi Bookoggetti . Al fine di implementare il addBook(), removeBook()e hasBook()metodi del carrello, ho voluto verificare se il Bookgià esisteva nel Cart. Quindi vado via -

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

Tutto funziona bene nei test. Creo 6 oggetti e li riempio di dati. Fai molte aggiunte, rimuove, ha () operazioni su Carte tutto funziona bene. Ho letto che puoi avere equals(TYPE var)oequals(Object o) { (CAST) var } ma supporre che dal momento che funzionava, non importava troppo.

Poi ho riscontrato un problema: avevo bisogno di creare un Bookoggetto con solo l' IDinterno all'interno della classe Book. Nessun altro dato verrebbe inserito al suo interno. Fondamentalmente il seguente:

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

Improvvisamente, il equals(Book b)metodo non funziona più. Questo ha impiegato MOLTO molto tempo per rintracciare senza un buon debugger e supponendo che la Cartclasse fosse correttamente testata e corretta. Dopo aver sostituito il equals()metodo nel modo seguente:

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

Tutto ha ripreso a funzionare. C'è una ragione per cui il metodo ha deciso di non prendere il parametro Book anche se chiaramente era un Bookoggetto? L'unica differenza sembrava essere stata istanziata all'interno della stessa classe e riempita con un solo membro di dati. Sono molto molto confuso. Per favore, fai luce?


1
Sono consapevole di aver violato il "Contratto" per quanto riguarda l'override dei metodi uguali essendo riflettente, tuttavia avevo bisogno di un modo rapido per verificare se l'oggetto esistesse nell'ArrayList senza usare generici.
Josh Smeaton,

1
Questa è una buona lezione per conoscere Java ed eguali
jjnguy,

Risposte:


329

In Java, il equals()metodo ereditato Objectè:

public boolean equals(Object other);

In altre parole, il parametro deve essere di tipo Object. Questo si chiama override ; il tuo metodo public boolean equals(Book other)fa quello che viene chiamato sovraccarico al equals()metodo.

Gli ArrayListusi ignorati equals()metodi per confrontare i contenuti (ad esempio per la sua contains()e equals()metodi), non quelli sovraccarico. Nella maggior parte del codice, chiamare quello che non ha scavalcato correttamente Objectequals andava bene, ma non è compatibile con ArrayList.

Pertanto, non ignorare correttamente il metodo può causare problemi.

Sostituisco equivale a quanto segue ogni volta:

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

L'uso @Overridedell'annotazione può aiutare moltissimo con errori sciocchi.

Usalo ogni volta che pensi di avere la precedenza su un metodo di super classe o interfaccia. In questo modo, se lo fai nel modo sbagliato, otterrai un errore di compilazione.


31
Questo è un buon argomento a favore dell'annotazione @Override ... se l'OP avesse usato @Override il suo compilatore gli avrebbe detto che in realtà non stava scavalcando un metodo di classe genitore ...
Cowan,

1
Non sono mai stato a conoscenza di @Override, grazie per quello! Vorrei anche aggiungere che la sovrascrittura di hashCode () avrebbe davvero dovuto essere eseguita e potrebbe aver individuato l'errore prima.
Josh Smeaton,

5
Alcuni IDE (ad esempio Eclipse) possono persino generare automaticamente i metodi equals () e hashcode () in base alle variabili dei membri della classe.
sk.

1
if (!(other instanceof MyClass))return false;ritorna falsese MyClassestende l'altra classe. Ma non ritornerebbe falsese l'altra classe si estendesse MyClass. Non dovrebbe equalessere meno contraddittorio?
Robert,

19
Quando si utilizza instanceof il controllo null precedente è ridondante.
Mateusz Dymczyk,

108

Se usi eclipse vai al menu principale

Sorgente -> Genera equals () e hashCode ()


Sono d'accordo! Questo che non avevo mai conosciuto prima e generarlo rende meno soggetto a errori
Boy

Anch'io. Grazie Fred!
Anila,

16
In IntelliJ lo trovi sotto Codice → Genera ... o control + N. :)
destra del

In Netbeans vai alla barra dei menu> Sorgente (o clic destro)> Inserisci codice (o Ctrl-I) e fai clic su Genera uguale () ...
Solomon,

11

Leggermente fuori tema per la tua domanda, ma probabilmente vale la pena menzionare comunque:

Commons Lang ha alcuni metodi eccellenti che puoi usare per sovrascrivere equals e hashcode. Dai un'occhiata a EqualsBuilder.reflectionEquals (...) e HashCodeBuilder.reflectionHashCode (...) . Mi ha risparmiato un sacco di mal di testa in passato - anche se ovviamente se vuoi solo fare "uguale" su ID potrebbe non adattarsi alle tue circostanze.

Sono anche d'accordo che dovresti usare l' @Overrideannotazione ogni volta che esegui l'override di uguale (o qualsiasi altro metodo).


4
Se sei un utente di eclissi, puoi anche andare right click -> source -> generate hashCode() and equals(),
tunaranch

1
Ho ragione che questo metodo viene eseguito in fase di esecuzione? Non avremo problemi di performance nel caso in cui attraversiamo una grande collezione con elementi che controllano la loro uguaglianza con qualche altro oggetto a causa della riflessione?
Gaket,

4

Un'altra soluzione rapida che consente di salvare il codice del boilerplate è l' annotazione Lombok EqualsAndHashCode . È facile, elegante e personalizzabile. E non dipende dall'IDE . Per esempio;

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;

Vedi le opzioni disponibili per personalizzare quali campi utilizzare in uguali. Lombok è disponibile in maven . Basta aggiungerlo con l' ambito fornito :

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

1

in Android Studio è alt + insert ---> equals e hashCode

Esempio:

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

1

Tener conto di:

Object obj = new Book();
obj.equals("hi");
// Oh noes! What happens now? Can't call it with a String that isn't a Book...

1
@Elazar Come? objè dichiarato come Object. Il punto di eredità è che puoi quindi assegnare un Booka obj. Dopodiché, a meno che tu non suggerisca che an Objectnon dovrebbe essere paragonabile a un Stringvia equals(), questo codice dovrebbe essere perfettamente legale e restituito false.
bcsb1001,

Suggerisco esattamente questo. Credo che sia ampiamente accettato.
Elazar,

0

l' instanceOfaffermazione viene spesso utilizzata nell'attuazione di uguali.

Questa è una trappola popolare!

Il problema è che l'uso instanceOfviola la regola della simmetria:

(object1.equals(object2) == true) se e solo se (object2.equals(object1))

se il primo uguale è vero e object2 è un'istanza di una sottoclasse della classe a cui appartiene obj1, il secondo uguale restituirà false!

se la classe considerata a cui appartiene ob1 è dichiarata come definitiva, allora questo problema non può sorgere, ma in generale, dovresti testare come segue:

this.getClass() != otherObject.getClass(); in caso contrario, restituisce false, altrimenti prova i campi per confrontare per l'uguaglianza!


3
Vedi Bloch, Effective Java, Item 8, una grande sezione che discute problemi con l'override del equals()metodo. Si sconsiglia di utilizzare getClass(). Il motivo principale è che così facendo si infrange il principio di sostituzione di Liskov per le sottoclassi che non incidono sull'uguaglianza.
Stuart segna il

-1

recordId è di proprietà dell'oggetto

@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;
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.