Quels problèmes / pièges doivent être pris en compte lors de la substitution d’ equals
et de hashCode
?
equals()
( javadoc ) doit définir une relation d’équivalence (elle doit être réflexive , symésortingque et transitive ). De plus, il doit être cohérent (si les objects ne sont pas modifiés, il doit continuer à renvoyer la même valeur). De plus, o.equals(null)
doit toujours retourner false.
hashCode()
( javadoc ) doit également être cohérent (si l’object n’est pas modifié en termes d’ equals()
, il doit continuer à retourner la même valeur).
La relation entre les deux méthodes est la suivante:
A chaque fois que
a.equals(b)
, alorsa.hashCode()
doit être identique àb.hashCode()
.
Si vous en remplacez un, vous devez alors passer outre l’autre.
Utilisez le même ensemble de champs que vous utilisez pour calculer equals()
pour calculer hashCode()
.
Utilisez les excellentes classes d’assistance EqualsBuilder et HashCodeBuilder de la bibliothèque Apache Commons Lang . Un exemple:
public class Person { private Ssortingng name; private int age; // ... @Override public int hashCode() { return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers // if deriving: appendSuper(super.hashCode()). append(name). append(age). toHashCode(); } @Override public boolean equals(Object obj) { if (!(obj instanceof Person)) return false; if (obj == this) return true; Person rhs = (Person) obj; return new EqualsBuilder(). // if deriving: appendSuper(super.equals(obj)). append(name, rhs.name). append(age, rhs.age). isEquals(); } }
Lorsque vous utilisez une collection ou une mappe basée sur un hachage, telle que HashSet , LinkedHashSet , HashMap , Hashtable ou WeakHashMap , assurez-vous que le hashCode () des objects clés que vous avez mis dans la collection ne change jamais lorsque l’object est dans la collection. La manière la plus simple de s’assurer de cela est de rendre vos clés immuables, ce qui présente également d’autres avantages .
Il y a quelques problèmes à noter si vous avez affaire à des classes persistantes utilisant un ORM (Object-Relationship Mapper) comme Hibernate, si vous ne pensiez pas que cela était déjà compliqué.
Les objects chargés paresseux sont des sous-classes
Si vos objects sont persistants à l’aide d’un ORM, dans de nombreux cas, vous devrez gérer des proxies dynamics pour éviter de charger l’object trop tôt dans le magasin de données. Ces proxies sont implémentés en tant que sous-classes de votre propre classe. Cela signifie que this.getClass() == o.getClass()
renverra false
. Par exemple:
Person saved = new Person("John Doe"); Long key = dao.save(saved); dao.flush(); Person resortingeved = dao.resortingeve(key); saved.getClass().equals(resortingeved.getClass()); // Will return false if Person is loaded lazy
Si vous avez affaire à un ORM, utiliser o instanceof Person
est la seule chose qui se comporte correctement.
Les objects chargés paresseux ont des champs nuls
Les ORM utilisent généralement les getters pour forcer le chargement d’objects chargés paresseux. Cela signifie que person.name
sera null
si person
est chargé paresseux, même si person.getName()
force le chargement et renvoie “John Doe”. D’après mon expérience, cela se produit plus souvent dans hashCode()
et equals()
.
Si vous traitez avec un ORM, assurez-vous de toujours utiliser les getters, et jamais les références de champs dans hashCode()
et equals()
.
L’enregistrement d’un object changera son état
Les objects persistants utilisent souvent un champ id
pour contenir la clé de l’object. Ce champ sera automatiquement mis à jour lorsqu’un object est enregistré pour la première fois. N’utilisez pas de champ id dans hashCode()
. Mais vous pouvez l’utiliser dans des equals()
.
Un modèle que j’utilise souvent est
if (this.getId() == null) { return this == other; } else { return this.getId().equals(other.getId()); }
Mais: vous ne pouvez pas inclure getId()
dans hashCode()
. Si vous le faites, lorsqu’un object est persisté, son hashCode
change. Si l’object est dans un HashSet
, vous ne le “retrouverez” plus jamais.
Dans mon exemple Person
, j’utiliserais probablement getName()
pour hashCode
et getId()
plus getName()
(juste pour la paranoïa) pour equals()
. Ce n’est pas grave s’il y a un risque de “collisions” pour hashCode()
, mais jamais pour equals()
.
hashCode()
devrait utiliser le sous-ensemble de propriétés non changeant de equals()
Une précision sur le obj.getClass() != getClass()
.
Cette instruction est le résultat de equals()
étant l’inheritance hostile. Le JLS (spécification du langage Java) spécifie que si A.equals(B) == true
alors B.equals(A)
doit également retourner true
. Si vous omettez cette instruction, hériter des classes qui remplacent equals()
(et modifier son comportement) enfreindra cette spécification.
Prenons l’exemple suivant de ce qui se passe lorsque l’énoncé est omis:
class A { int field1; A(int field1) { this.field1 = field1; } public boolean equals(Object other) { return (other != null && other instanceof A && ((A) other).field1 == field1); } } class B extends A { int field2; B(int field1, int field2) { super(field1); this.field2 = field2; } public boolean equals(Object other) { return (other != null && other instanceof B && ((B)other).field2 == field2 && super.equals(other)); } }
Faire de new A(1).equals(new A(1))
En outre, les new B(1,1).equals(new B(1,1))
sont vrais, comme il se doit.
Cela semble très bien, mais regardez ce qui se passe si nous essayons d’utiliser les deux classes:
A a = new A(1); B b = new B(1,1); a.equals(b) == true; b.equals(a) == false;
De toute évidence, c’est faux.
Si vous voulez assurer la condition symésortingque. a = b si b = a et le principe de substitution de Liskov appelle super.equals(other)
non seulement dans le cas de l’instance B
, mais vérifie après pour l’instance A
:
if (other instanceof B ) return (other != null && ((B)other).field2 == field2 && super.equals(other)); if (other instanceof A) return super.equals(other); else return false;
Qui va sortir:
a.equals(b) == true; b.equals(a) == true;
Où, si a
n’est pas une référence de B
, alors ce pourrait être une référence de classe A
(parce que vous l’ super.equals()
), dans ce cas vous appelez aussi super.equals()
.
Pour une implémentation compatible avec l’inheritance, consultez la solution de Tal Cohen, Comment implémenter correctement la méthode equals ()?
Résumé:
Dans son livre Effective Java Programming Language Guide (Addison-Wesley, 2001), Joshua Bloch affirme qu ‘”il n’y a tout simplement aucun moyen d’étendre une classe instanciable et d’append un aspect tout en préservant le contrat égal.” Tal n’est pas d’accord.
Sa solution est d’implémenter equals () en appelant un autre non-symésortingque blindlyEquals () dans les deux sens. blindlyEquals () est remplacé par les sous-classes, equals () est hérité et jamais remplacé.
Exemple:
class Point { private int x; private int y; protected boolean blindlyEquals(Object o) { if (!(o instanceof Point)) return false; Point p = (Point)o; return (px == this.x && py == this.y); } public boolean equals(Object o) { return (this.blindlyEquals(o) && o.blindlyEquals(this)); } } class ColorPoint extends Point { private Color c; protected boolean blindlyEquals(Object o) { if (!(o instanceof ColorPoint)) return false; ColorPoint cp = (ColorPoint)o; return (super.blindlyEquals(cp) && cp.color == this.color); } }
Notez que equals () doit fonctionner sur les hiérarchies d’inheritance si le principe de substitution Liskov doit être satisfait.
Encore étonné que personne ne recommande la bibliothèque de goyave pour cela.
//Sample taken from a current working project of mine just to illustrate the idea @Override public int hashCode(){ return Objects.hashCode(this.getDate(), this.datePattern); } @Override public boolean equals(Object obj){ if ( ! obj instanceof DateAndPattern ) { return false; } return Objects.equal(((DateAndPattern)obj).getDate(), this.getDate()) && Objects.equal(((DateAndPattern)obj).getDate(), this.getDatePattern()); }
Il y a deux méthodes dans la super classe comme java.lang.Object. Nous devons les remplacer par un object personnalisé.
public boolean equals(Object obj) public int hashCode()
Les objects égaux doivent produire le même code de hachage tant qu’ils sont égaux, mais les objects inégaux n’ont pas besoin de produire des codes de hachage distincts.
public class Test { private int num; private Ssortingng data; public boolean equals(Object obj) { if(this == obj) return true; if((obj == null) || (obj.getClass() != this.getClass())) return false; // object must be Test at this point Test test = (Test)obj; return num == test.num && (data == test.data || (data != null && data.equals(test.data))); } public int hashCode() { int hash = 7; hash = 31 * hash + num; hash = 31 * hash + (null == data ? 0 : data.hashCode()); return hash; } // other methods }
Si vous voulez obtenir plus, s’il vous plaît vérifier ce lien sous http://www.javaranch.com/journal/2002/10/equalhash.html
Ceci est un autre exemple, http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html
S’amuser! @. @
Il existe plusieurs façons de vérifier votre égalité de classe avant de vérifier l’égalité des membres, et je pense que les deux sont utiles dans les bonnes circonstances.
instanceof
. this.getClass().equals(that.getClass())
. J’utilise # 1 dans une implémentation d’égaux final
ou lors de l’implémentation d’une interface qui prescrit un algorithme pour les égaux (comme les interfaces de collection java.util
– la bonne façon de vérifier avec (obj instanceof Set)
ou l’interface que vous implémentez) . C’est généralement un mauvais choix lorsque les égaux peuvent être remplacés, car cela rompt la propriété de symésortinge.
L’option 2 permet à la classe d’être étendue en toute sécurité sans surcharger les symésortinges ou les égaler.
Si votre classe est également Comparable
, les méthodes equals
et compareTo
devraient également être cohérentes. Voici un modèle pour la méthode égale dans une classe Comparable
:
final class MyClass implements Comparable { … @Override public boolean equals(Object obj) { /* If compareTo and equals aren't final, we should check with getClass instead. */ if (!(obj instanceof MyClass)) return false; return compareTo((MyClass) obj) == 0; } }
Pour les égaux, regardez les secrets d’ Angelika Langer . Je l’aime beaucoup. C’est aussi une très bonne FAQ sur les génériques en Java . Consultez ses autres articles ici (faites défiler jusqu’à «Core Java»), où elle continue également avec la partie 2 et la «comparaison de types mixtes». Amusez-vous à les lire!
La méthode equals () est utilisée pour déterminer l’égalité de deux objects.
comme int la valeur de 10 est toujours égale à 10. Mais cette méthode equals () concerne l’égalité de deux objects. Lorsque nous disons object, il aura des propriétés. Pour décider de l’égalité, ces propriétés sont considérées. Il n’est pas nécessaire que toutes les propriétés doivent être sockets en compte pour déterminer l’égalité et en ce qui concerne la définition de la classe et le contexte, il peut être décidé. La méthode equals () peut alors être remplacée.
nous devons toujours remplacer la méthode hashCode () chaque fois que nous substituons la méthode equals (). Sinon, que va-t-il se passer? Si nous utilisons des hashtables dans notre application, cela ne se comportera pas comme prévu. Comme le hashCode est utilisé pour déterminer l’égalité des valeurs stockées, il ne renverra pas la valeur correspondante correcte pour une clé.
L’implémentation par défaut donnée est la méthode hashCode () dans la classe Object qui utilise l’adresse interne de l’object et la convertit en entier et la renvoie.
public class Tiger { private Ssortingng color; private Ssortingng ssortingpePattern; private int height; @Override public boolean equals(Object object) { boolean result = false; if (object == null || object.getClass() != getClass()) { result = false; } else { Tiger tiger = (Tiger) object; if (this.color == tiger.getColor() && this.ssortingpePattern == tiger.getSsortingpePattern()) { result = true; } } return result; } // just omitted null checks @Override public int hashCode() { int hash = 3; hash = 7 * hash + this.color.hashCode(); hash = 7 * hash + this.ssortingpePattern.hashCode(); return hash; } public static void main(Ssortingng args[]) { Tiger bengalTiger1 = new Tiger("Yellow", "Dense", 3); Tiger bengalTiger2 = new Tiger("Yellow", "Dense", 2); Tiger siberianTiger = new Tiger("White", "Sparse", 4); System.out.println("bengalTiger1 and bengalTiger2: " + bengalTiger1.equals(bengalTiger2)); System.out.println("bengalTiger1 and siberianTiger: " + bengalTiger1.equals(siberianTiger)); System.out.println("bengalTiger1 hashCode: " + bengalTiger1.hashCode()); System.out.println("bengalTiger2 hashCode: " + bengalTiger2.hashCode()); System.out.println("siberianTiger hashCode: " + siberianTiger.hashCode()); } public Ssortingng getColor() { return color; } public Ssortingng getSsortingpePattern() { return ssortingpePattern; } public Tiger(Ssortingng color, Ssortingng ssortingpePattern, int height) { this.color = color; this.ssortingpePattern = ssortingpePattern; this.height = height; } }
Exemple de sortie de code:
bengalTiger1 and bengalTiger2: true bengalTiger1 and siberianTiger: false bengalTiger1 hashCode: 1398212510 bengalTiger2 hashCode: 1398212510 siberianTiger hashCode: –1227465966
Logiquement, nous avons:
a.getClass().equals(b.getClass()) && a.equals(b)
⇒ a.hashCode() == b.hashCode()
Mais pas l’ inverse!
Un piège que j’ai trouvé est celui où deux objects contiennent des références les uns aux autres (un exemple étant une relation parent / enfant avec une méthode pratique sur le parent pour obtenir tous les enfants).
Ce genre de choses est assez courant lors des mappages Hibernate par exemple.
Si vous incluez les deux extrémités de la relation dans vos tests hashCode ou égale, il est possible d’entrer dans une boucle récursive qui se termine par une exception StackOverflowException.
La solution la plus simple consiste à ne pas inclure la collection getChildren dans les méthodes.