web-dev-qa-db-fra.com

Redéfinition de la méthode Java equals () - ne fonctionne pas?

J'ai rencontré un problème intéressant (et très frustrant) avec la méthode equals() aujourd'hui, ce qui a provoqué la chute de ce que je pensais être une classe bien testée et un bug qui m'a pris beaucoup de temps à localiser.

Juste pour être complet, je n'utilisais pas un IDE ou un débogueur - seulement un bon éditeur de texte à l'ancienne et celui de System.out. Le temps était très limité et c'était un projet d'école.

De toute façon -

Je développais un panier d'achat de base pouvant contenir un ArrayList of Book objets. Afin de mettre en œuvre les méthodes addBook(), removeBook() et hasBook() du panier, je voulais vérifier si le Book existait déjà dans le Cart. Alors je pars -

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

Tout fonctionne bien dans les tests. Je crée 6 objets et les remplis de données. Faites beaucoup d’ajouts, de suppressions, d’opérations () sur le Cart et tout fonctionne bien. J'ai lu que vous pouvez soit avoir equals(TYPE var) ou equals(Object o) { (CAST) var } mais je suppose que puisque cela fonctionnait, cela importait peu.

Ensuite, j'ai rencontré un problème - je devais créer un objet Book avec niquement le ID à partir de la classe Book. Aucune autre donnée n'y serait entrée. Fondamentalement ce qui suit:

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

Tout à coup, la méthode equals(Book b) ne fonctionne plus. Cela a pris TRES longtemps pour rechercher sans un bon débogueur et en supposant que la classe Cart était correctement testée et correcte. Après avoir modifié la méthode equals(), procédez comme suit:

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

Tout a recommencé à fonctionner. Y at-il une raison pour laquelle la méthode a décidé de ne pas prendre le paramètre Book même s’il est clairement était un objet Book? La seule différence semblait être qu'elle était instanciée à partir de la même classe et remplie avec un seul membre de données. Je suis très très confus. S'il vous plaît, donnez un peu de lumière?

148
Josh Smeaton

En Java, la méthode equals() héritée de Object est la suivante:

public boolean equals(Object other);

En d'autres termes, le paramètre doit être de type Object.

Le ArrayList utilise la bonne méthode égal à, où vous appeliez toujours celui qui ne remplaçait pas correctement les égaux de Object.

Ne pas écraser correctement la méthode peut causer des problèmes.

Je remplace égale à chaque fois la suivante:

@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’utilisation de l’annotation @Override peut aider énormément d’erreurs stupides.

Utilisez-le chaque fois que vous pensez que vous substituez la méthode d'une super classe ou d'une interface. De cette façon, si vous le faites mal, vous obtiendrez une erreur de compilation.

325
jjnguy

Si vous utilisez Eclipse, allez simplement au menu du haut

Source -> Générer equals () et hashCode ()

108
Fred

Un peu hors sujet par rapport à votre question, mais ça vaut probablement la peine de le mentionner quand même:

Commons Lang dispose d'excellentes méthodes que vous pouvez utiliser pour remplacer les égaux et le hashcode. Vérifiez EqualsBuilder.reflectionEquals (...) et HashCodeBuilder.reflectionHashCode (...) . M'a sauvé beaucoup de maux de tête dans le passé - bien que bien sûr, si vous voulez juste faire "égal" sur ID, cela peut ne pas correspondre à votre situation.

Je conviens également que vous devriez utiliser l'annotation @Override chaque fois que vous substituez égaux (ou toute autre méthode).

11
user7094

Une autre solution rapide qui enregistre le code standard est annotation Lombok EqualsAndHashCode . C'est facile, élégant et personnalisable. Et ne dépend pas de l'IDE . Par exemple;

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;

Voir le options disponible pour personnaliser les champs à utiliser dans les égaux. Lombok est disponible dans maven . Ajoutez-le simplement avec fourni

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

Considérer:

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
bcsb1001

dans Android Studio est alt + insert ---> est égal à hashCode

Exemple:

    @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
David Hackro

l'instruction instanceOf est souvent utilisée dans l'implémentation d'égal à égal.

C'est un piège populaire!

Le problème est que l'utilisation de instanceOf enfreint la règle de symétrie:

(object1.equals(object2) == true)si et seulement si(object2.equals(object1))

si le premier égal est vrai et que objet2 est une instance d'une sous-classe de la classe à laquelle obj1 appartient, alors le deuxième égal retournera faux!

si la classe considérée à laquelle ob1 appartient est déclarée comme finale, alors ce problème ne peut pas se produire, mais en général, vous devriez tester comme suit:

this.getClass() != otherObject.getClass(); sinon, retourne false, sinon teste les champs à comparer pour l'égalité!

0
Nikel8000