Überschreiben der Java-Methode equals () - funktioniert das nicht?

150

Ich bin heute auf ein interessantes (und sehr frustrierendes) Problem mit der equals()Methode gestoßen, das dazu führte, dass eine meiner Meinung nach gut getestete Klasse abstürzte und einen Fehler verursachte, dessen Aufspüren sehr lange gedauert hat.

Der Vollständigkeit halber habe ich keine IDE oder keinen Debugger verwendet - nur einen guten, altmodischen Texteditor und System.out's. Die Zeit war sehr begrenzt und es war ein Schulprojekt.

Wie auch immer -

Ich entwickelte einen einfachen Einkaufswagen, ArrayListder BookObjekte enthalten konnte . Um das ,, und die Methoden des Cart zu implementieren addBook(), wollte ich überprüfen, ob das bereits im vorhanden ist . Also los geht's -removeBook()hasBook()BookCart

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

Beim Testen funktioniert alles einwandfrei. Ich erstelle 6 Objekte und fülle sie mit Daten. Führen Sie viele Operationen zum Hinzufügen, Entfernen und Ausführen von () durch, Cartund alles funktioniert einwandfrei. Ich habe gelesen, dass man entweder haben kann equals(TYPE var)oderequals(Object o) { (CAST) var } aber angenommen hat, dass es nicht allzu wichtig ist, da es funktioniert.

Dann stieß ich auf ein Problem - ich musste ein BookObjekt mit nur dem IDdarin aus der Book-Klasse erstellen . Es würden keine anderen Daten eingegeben. Grundsätzlich gilt Folgendes:

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

Plötzlich equals(Book b)funktioniert die Methode nicht mehr. Das Aufspüren ohne einen guten Debugger dauerte SEHR lange und vorausgesetzt, die CartKlasse wurde ordnungsgemäß getestet und korrekt. Nach dem Austausch der equals()Methode auf Folgendes:

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

Alles begann wieder zu funktionieren. Gibt es einen Grund, warum die Methode beschlossen hat, den Book-Parameter nicht zu verwenden, obwohl es sich eindeutig um ein BookObjekt handelt? Der einzige Unterschied schien darin zu bestehen, dass es aus derselben Klasse heraus instanziiert und nur mit einem Datenelement gefüllt wurde. Ich bin sehr sehr verwirrt. Bitte, etwas Licht ins Dunkel bringen?

Josh Smeaton
quelle
1
Mir ist bekannt, dass ich gegen den 'Vertrag' bezüglich des Überschreibens der Gleichheitsmethoden verstoßen habe, indem ich reflektiert habe. Ich brauchte jedoch eine schnelle Möglichkeit, um zu überprüfen, ob das Objekt in der ArrayList vorhanden war, ohne Generika zu verwenden.
Josh Smeaton
1
Dies ist eine gute Lektion, um etwas über Java und Gleichberechtigung zu lernen
jjnguy

Antworten:

329

In Java wird folgende equals()Methode geerbt Object:

public boolean equals(Object other);

Mit anderen Worten, der Parameter muss vom Typ sein Object. Dies wird als Überschreiben bezeichnet . Ihre Methode public boolean equals(Book other)führt eine sogenannte Überladung der equals()Methode durch.

Das ArrayListverwendet überschriebene equals()Methoden, um Inhalte zu vergleichen (z. B. für seine contains()und equals()Methoden), nicht überladene. In den meisten Ihrer Codes Objectwar es in Ordnung, denjenigen aufzurufen, der die Gleichheit nicht richtig überschrieb , aber nicht kompatibel mit ArrayList.

Wenn Sie die Methode nicht richtig überschreiben, kann dies zu Problemen führen.

Ich überschreibe jedes Mal Folgendes:

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

Die Verwendung der @OverrideAnmerkung kann einer Tonne bei dummen Fehlern helfen.

Verwenden Sie es immer dann, wenn Sie glauben, die Methode einer Superklasse oder Schnittstelle zu überschreiben. Auf diese Weise erhalten Sie einen Kompilierungsfehler, wenn Sie es falsch machen.

jjnguy
quelle
31
Dies ist ein gutes Argument für die Annotation @Override ... Wenn das OP @Override verwendet hätte, hätte ihm sein Compiler gesagt, dass er eine übergeordnete Klassenmethode nicht tatsächlich überschreibt ...
Cowan
1
War mir der @Override nie bewusst, danke dafür! Ich möchte auch hinzufügen, dass das Überschreiben von hashCode () eigentlich hätte erfolgen sollen und den Fehler möglicherweise früher entdeckt hat.
Josh Smeaton
5
Einige IDEs (z. B. Eclipse) können sogar die Methoden equals () und hashcode () basierend auf den Variablen der Klassenmitglieder automatisch generieren.
sk.
1
if (!(other instanceof MyClass))return false;Gibt zurück, falsewenn MyClassdie andere Klasse erweitert wird. Aber es würde nicht zurückkehren, falsewenn die andere Klasse erweitert würde MyClass. Sollte nicht equalweniger widersprüchlich sein?
Robert
19
Bei Verwendung der Instanz des vorherigen Nullchecks ist redundant.
Mateusz Dymczyk
108

Wenn Sie Eclipse verwenden, gehen Sie einfach zum Hauptmenü

Quelle -> Generiere equals () und hashCode ()

Fred
quelle
Genau! Dieser, von dem ich noch nie zuvor gewusst habe und der ihn generiert, macht ihn weniger fehleranfällig
Boy
Hier gilt das gleiche. Danke Fred!
Anila
16
In IntelliJ finden Sie dies unter Code → Generieren… oder Strg + N. :)
Rechtsfalte
In Netbeans gehen Sie zur Menüleiste> Quelle (oder Rechtsklick)> Code einfügen (oder Strg-I) und klicken Sie auf Generate equals () ...
Solomon
11

Etwas abseits des Themas zu Ihrer Frage, aber es ist wahrscheinlich trotzdem erwähnenswert:

Commons Lang verfügt über einige hervorragende Methoden, mit denen Sie Equals und Hashcode überschreiben können. Schauen Sie sich EqualsBuilder.reflectionEquals (...) und HashCodeBuilder.reflectionHashCode (...) an . Hat mir in der Vergangenheit viel Kopfschmerzen erspart - obwohl es natürlich nicht Ihren Umständen entspricht, wenn Sie nur "gleich" mit dem Ausweis machen möchten.

Ich bin auch damit einverstanden, dass Sie die @OverrideAnnotation verwenden sollten, wenn Sie gleich (oder eine andere Methode) überschreiben.


quelle
4
Wenn Sie ein Eclipse-Benutzer sind, können Sie auch gehen right click -> source -> generate hashCode() and equals(),
tunaranch
1
Habe ich recht, dass diese Methode zur Laufzeit ausgeführt wird? Haben wir keine Leistungsprobleme für den Fall, dass wir eine große Sammlung mit Elementen durchlaufen, die aufgrund von Überlegungen auf Gleichheit mit einem anderen Element überprüft werden?
Gaket
4

Eine weitere schnelle Lösung, die Boilerplate-Code speichert, ist die Annotation Lombok EqualsAndHashCode . Es ist einfach, elegant und anpassbar. Und hängt nicht von der IDE ab . Beispielsweise;

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;

Sehen Sie sich die verfügbaren Optionen an, um anzupassen, welche Felder gleich verwendet werden sollen. Lombok ist in Maven verfügbar . Fügen Sie es einfach mit zur Verfügung gestellt Umfang:

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

in Android Studio ist alt + insert ---> gleich und hashCode

Beispiel:

    @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
quelle
1

Erwägen:

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
quelle
1
@ Elzar Wie so? objwird als deklariert Object. Der Punkt der Vererbung ist , dass man dann eine zuweisen Bookzu obj. Danach Objectsollte dieser Code vollkommen legal sein und zurückkehren , es sei denn, Sie schlagen vor, dass ein nicht mit einem StringVia vergleichbar sein equals()sollte false.
bcsb1001
Ich schlage genau das vor. Ich glaube, dass es ziemlich weit verbreitet ist.
Elazar
0

Die instanceOfAussage wird häufig bei der Implementierung von Gleichen verwendet.

Dies ist eine beliebte Falle!

Das Problem ist, dass die Verwendung instanceOfdie Symmetrieregel verletzt:

(object1.equals(object2) == true) dann und nur dann, wenn (object2.equals(object1))

Wenn die erste Gleichheit wahr ist und Objekt2 eine Instanz einer Unterklasse der Klasse ist, zu der obj1 gehört, gibt die zweite Gleichheit falsch zurück!

Wenn die betrachtete Klasse, zu der ob1 gehört, als endgültig deklariert ist, kann dieses Problem nicht auftreten. Im Allgemeinen sollten Sie jedoch Folgendes testen:

this.getClass() != otherObject.getClass(); Wenn nicht, geben Sie false zurück, andernfalls testen Sie die Felder, um sie auf Gleichheit zu vergleichen!

Nikel8000
quelle
3
Siehe Bloch, Effective Java, Punkt 8, einen großen Abschnitt, in dem Probleme beim Überschreiben der equals()Methode erläutert werden. Er rät von der Verwendung ab getClass(). Der Hauptgrund ist, dass dies gegen das Liskov-Substitutionsprinzip für Unterklassen verstößt, die die Gleichheit nicht beeinträchtigen.
Stuart Marks
-1

recordId ist eine Eigenschaft des Objekts

@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
quelle