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?
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.
Si vous utilisez Eclipse, allez simplement au menu du haut
Source -> Générer equals () et hashCode ()
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).
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>
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...
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();
}
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é!