Je suis tombé sur un problème intéressant (et très frustrant) avec la equals()
méthode aujourd'hui, ce qui a provoqué le crash de ce que je pensais être une classe bien testée et un bug qui m'a pris beaucoup de temps à retrouver.
Juste pour être complet, je n'utilisais pas un IDE ou un débogueur - juste un bon éditeur de texte à l'ancienne et System.out. Le temps était très limité et c'était un projet scolaire.
De toute façon -
Je développais un panier de base qui pourrait contenir un ArrayList
des Book
objets . Afin de mettre en œuvre les addBook()
, removeBook()
et les hasBook()
méthodes 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 has () sur le Cart
et tout fonctionne bien. J'ai lu que vous pouvez avoir equals(TYPE var)
ouequals(Object o) { (CAST) var }
mais supposer que puisque cela fonctionnait, cela n'avait pas trop d'importance.
Ensuite, j'ai rencontré un problème - je devais créer un Book
objet avec uniquement le ID
contenu de la classe Book. Aucune autre donnée n'y serait introduite. 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);
}
Du coup, la equals(Book b)
méthode ne fonctionne plus. Cela a pris TRÈS longtemps à traquer sans un bon débogueur et en supposant que la Cart
classe était correctement testée et correcte. Après avoir permuté la equals()
méthode à la suivante:
public boolean equals(Object o) {
Book b = (Book) o;
... // The rest goes here
}
Tout a recommencé à fonctionner. Y a-t-il une raison pour laquelle la méthode a décidé de ne pas prendre le paramètre Book alors qu'il s'agissait clairement d' un Book
objet? La seule différence semblait être qu'elle était instanciée à partir de la même classe et remplie uniquement avec un membre de données. Je suis très très confus. S'il vous plaît, faites la lumière?
la source
Réponses:
En Java, la
equals()
méthode héritéeObject
est:En d'autres termes, le paramètre doit être de type
Object
. C'est ce qu'on appelle le remplacement ; votre méthodepublic boolean equals(Book other)
fait ce qu'on appelle une surcharge de laequals()
méthode.Le
ArrayList
utilise desequals()
méthodes surchargées pour comparer le contenu (par exemple pour ses méthodescontains()
etequals()
), pas celles surchargées. Dans la plupart de votre code, appeler celui qui ne remplaçait pas correctementObject
les égaux était correct , mais pas compatible avecArrayList
.Ainsi, ne pas remplacer correctement la méthode peut causer des problèmes.
Je remplace à chaque fois ce qui suit:
L'utilisation de l'
@Override
annotation peut aider une tonne d'erreurs stupides.Utilisez-le chaque fois que vous pensez remplacer la méthode d'une super classe ou d'une interface. De cette façon, si vous ne le faites pas correctement, vous obtiendrez une erreur de compilation.
la source
if (!(other instanceof MyClass))return false;
retournefalse
siMyClass
étend l'autre classe. Mais il ne reviendrait pasfalse
si l'autre classe s'étendaitMyClass
. Ne devrait pasequal
être moins contradictoire?Si vous utilisez eclipse, allez simplement dans le menu du haut
la source
Légèrement hors sujet par rapport à votre question, mais cela vaut probablement la peine de le mentionner quand même:
Commons Lang a d'excellentes méthodes que vous pouvez utiliser pour remplacer les égaux et le hashcode. Découvrez 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 suis également d'accord pour que vous utilisiez l'
@Override
annotation chaque fois que vous remplacez égal (ou toute autre méthode).la source
right click -> source -> generate hashCode() and equals()
,Une autre solution rapide qui enregistre le code standard est l'annotation Lombok EqualsAndHashCode . C'est simple, élégant et personnalisable. Et ne dépend pas de l'IDE . Par exemple;
Voir les options disponibles pour personnaliser les champs à utiliser dans les égaux. Lombok est disponible en maven . Ajoutez-le simplement avec la portée fournie :
la source
dans Android Studio est alt + insert ---> equals et hashCode
Exemple:
la source
Considérer:
la source
obj
est déclaré comme unObject
. Le point d'héritage est que vous pouvez ensuite attribuer unBook
àobj
. Après cela, à moins que vous ne suggériez qu'unObject
ne soit pas comparable à unString
viaequals()
, ce code doit être parfaitement légal et revenirfalse
.l'
instanceOf
instruction est souvent utilisée dans l'implémentation d'égaux.C'est un piège populaire!
Le problème est que l'utilisation
instanceOf
viole 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 object2 est une instance d'une sous-classe de la classe à laquelle obj1 appartient, alors le second égal retournera faux!
si la classe considérée à laquelle appartient ob1 est déclarée comme finale, alors ce problème ne peut pas se poser, mais en général, vous devez tester comme suit:
this.getClass() != otherObject.getClass();
sinon, retournez false, sinon testez les champs à comparer pour l'égalité!la source
equals()
méthode. Il recommande de ne pas utilisergetClass()
. La raison principale est que cela enfreint le principe de substitution de Liskov pour les sous-classes qui n'affectent pas l'égalité.recordId est la propriété de l'objet
la source