Quels problèmes / pièges doivent être pris en compte lors de la substitution equals
et hashCode
?
Quels problèmes / pièges doivent être pris en compte lors de la substitution equals
et hashCode
?
Réponses:
equals()
( javadoc ) doit définir une relation d'équivalence (elle doit être réflexive , symétrique et transitive ). De plus, il doit être cohérent (si les objets ne sont pas modifiés, alors il doit continuer à renvoyer la même valeur). En outre, o.equals(null)
doit toujours retourner faux.
hashCode()
( javadoc ) doit également être cohérent (si l'objet n'est pas modifié en termes de equals()
, il doit continuer à renvoyer la même valeur).
La relation entre les deux méthodes est:
Chaque fois
a.equals(b)
, alorsa.hashCode()
doit être le même queb.hashCode()
.
Si vous remplacez l'un, vous devez remplacer 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 String 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 carte de hachage telle que HashSet , LinkedHashSet , HashMap , Hashtable ou WeakHashMap , assurez-vous que le hashCode () des objets clés que vous placez dans la collection ne change jamais tant que l'objet se trouve dans la collection. La manière à l'épreuve des balles de garantir cela est de rendre vos clés immuables, ce qui présente également d'autres avantages .
instanceof
renvoie false si son premier opérande est null (Java effectif à nouveau).
Il y a quelques problèmes à noter si vous traitez avec des classes qui persistent avec un mappeur de relation d'objet (ORM) comme Hibernate, si vous ne pensiez pas que c'était déjà déraisonnablement compliqué!
Les objets chargés paresseux sont des sous-classes
Si vos objets sont persistés à l'aide d'un ORM, dans de nombreux cas, vous aurez affaire à des proxys dynamiques pour éviter de charger l'objet trop tôt à partir du magasin de données. Ces proxys sont implémentés en tant que sous-classes de votre propre classe. Cela signifie que this.getClass() == o.getClass()
cela reviendra false
. Par exemple:
Person saved = new Person("John Doe");
Long key = dao.save(saved);
dao.flush();
Person retrieved = dao.retrieve(key);
saved.getClass().equals(retrieved.getClass()); // Will return false if Person is loaded lazy
Si vous avez affaire à un ORM, l'utilisation o instanceof Person
est la seule chose qui se comportera correctement.
Les objets chargés paresseux ont des champs nuls
Les ORM utilisent généralement les getters pour forcer le chargement d'objets chargés paresseux. Cela signifie que ce person.name
sera null
si person
est chargé paresseusement, même si person.getName()
force le chargement et renvoie "John Doe". D'après mon expérience, cela revient plus souvent dans hashCode()
et equals()
.
Si vous avez affaire à un ORM, assurez-vous de toujours utiliser des getters et de ne jamais mettre en référence des références dans hashCode()
et equals()
.
L'enregistrement d'un objet changera son état
Les objets persistants utilisent souvent un id
champ pour contenir la clé de l'objet. Ce champ sera automatiquement mis à jour lors de la première sauvegarde d'un objet. N'utilisez pas de champ id dans hashCode()
. Mais vous pouvez l'utiliser dans equals()
.
Un motif 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 objet persiste, ses hashCode
modifications. Si l'objet est dans un HashSet
, vous ne le retrouverez "plus" jamais.
Dans mon Person
exemple, 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" hashCode()
, mais jamais correct equals()
.
hashCode()
doit utiliser le sous-ensemble de propriétés inchangé de equals()
Saving an object will change it's state
! hashCode
doit revenir int
, alors comment allez-vous utiliser getName()
? Pouvez-vous donner un exemple pour votrehashCode
Une clarification sur le obj.getClass() != getClass()
.
Cette déclaration est le résultat d' equals()
un héritage 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éritant des classes qui remplacent equals()
(et modifient son comportement), cette spécification sera rompue.
Prenons l'exemple suivant de ce qui se passe lorsque l'instruction est omise:
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 new A(1).equals(new A(1))
aussi, le new B(1,1).equals(new B(1,1))
résultat donne vrai, 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étrique. a = b si b = a et le principe de substitution de Liskov appellent super.equals(other)
non seulement dans le cas de l' B
instance, mais vérifient après par A
exemple:
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 produira:
a.equals(b) == true;
b.equals(a) == true;
Où, si ce 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'étendez), dans ce cas vous appelez super.equals()
aussi .
ThingWithOptionSetA
peut être égal à a à Thing
condition que toutes les options supplémentaires aient des valeurs par défaut, et de même pour a ThingWithOptionSetB
, alors il devrait être possible pour a ThingWithOptionSetA
d'être comparé égal à a ThingWithOptionSetB
seulement si toutes les propriétés non-base des deux objets correspondent à leurs valeurs par défaut, mais Je ne vois pas comment vous testez cela.
B b2 = new B(1,99)
, puis b.equals(a) == true
et a.equals(b2) == true
mais b.equals(b2) == false
.
Pour une implémentation respectueuse de l'héritage, consultez la solution de Tal Cohen, Comment puis-je implémenter correctement la méthode equals ()?
Sommaire:
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'ajouter un aspect tout en préservant le contrat égal". Tal n'est pas d'accord.
Sa solution consiste à implémenter equals () en appelant un autre aveuglément non symétrique Equals () dans les deux sens. blindlyEquals () est écrasé par les sous-classes, equals () est hérité et n'est jamais écrasé.
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 (p.x == this.x && p.y == 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 à travers les hiérarchies d'héritage si le principe de substitution de Liskov doit être satisfait.
if (this.getClass() != o.getClass()) return false
, mais flexible en ce qu'il ne renvoie false que si la ou les classes dérivées prennent la peine de modifier égal. Est-ce correct?
Toujours étonné qu'aucun 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());
}
this
au this.getDate()
moyen de rien (autre que le désordre)
if (!(otherObject instanceof DateAndPattern)) {
. D'accord avec hernan et Steve Kuo (bien que ce soit une question de préférence personnelle), mais +1 néanmoins.
Il existe deux méthodes dans la super classe comme java.lang.Object. Nous devons les remplacer par un objet personnalisé.
public boolean equals(Object obj)
public int hashCode()
Les objets égaux doivent produire le même code de hachage tant qu'ils sont égaux, mais les objets inégaux n'ont pas besoin de produire des codes de hachage distincts.
public class Test
{
private int num;
private String 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 en savoir plus, veuillez consulter 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 y a deux 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
opérateur.this.getClass().equals(that.getClass())
.J'utilise # 1 dans une final
implémentation égale, ou lors de l'implémentation d'une interface qui prescrit un algorithme pour égal (comme les java.util
interfaces de collecte - la bonne façon de vérifier avec (obj instanceof Set)
ou quelle que soit l'interface que vous implémentez). C'est généralement un mauvais choix lorsque des égaux peuvent être remplacés car cela rompt la propriété de symétrie.
L'option n ° 2 permet d'étendre la classe en toute sécurité sans remplacer les égaux ni rompre la symétrie.
Si votre classe l'est également Comparable
, les méthodes equals
et compareTo
doivent également être cohérentes. Voici un modèle pour la méthode equals dans une Comparable
classe:
final class MyClass implements Comparable<MyClass>
{
…
@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;
}
}
final
et que la compareTo()
méthode a été remplacée pour inverser l'ordre de tri, les instances de la sous-classe et de la superclasse ne devraient pas être considérées comme égales. Lorsque ces objets étaient utilisés ensemble dans une arborescence, les clés qui étaient «égales» selon une instanceof
implémentation pouvaient ne pas être trouvées.
Pour des égaux, regardez dans Secrets of Equals par Angelika Langer . Je l'aime beaucoup. Elle est également une excellente FAQ sur les génériques en Java . Voir 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 objets.
car la valeur int de 10 est toujours égale à 10. Mais cette méthode equals () concerne l'égalité de deux objets. Quand nous disons objet, il aura des propriétés. Pour décider de l'égalité, ces propriétés sont prises en compte. Il n'est pas nécessaire que toutes les propriétés soient prises en compte pour déterminer l'égalité et en ce qui concerne la définition de classe et le contexte, il peut être décidé. Ensuite, la méthode equals () peut être remplacée.
nous devons toujours remplacer la méthode hashCode () chaque fois que nous substituons la méthode equals (). Sinon, que se passera-t-il? Si nous utilisons des tables de hachage dans notre application, elle 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 bonne valeur correspondante pour une clé.
L'implémentation par défaut donnée est la méthode hashCode () dans la classe Object utilise l'adresse interne de l'objet et la convertit en entier et la renvoie.
public class Tiger {
private String color;
private String stripePattern;
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.stripePattern == tiger.getStripePattern()) {
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.stripePattern.hashCode();
return hash;
}
public static void main(String 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 String getColor() {
return color;
}
public String getStripePattern() {
return stripePattern;
}
public Tiger(String color, String stripePattern, int height) {
this.color = color;
this.stripePattern = stripePattern;
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
Un problème que j'ai trouvé est celui où deux objets contiennent des références l'un à l'autre (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 votre hashCode ou des tests égaux, il est possible d'entrer dans une boucle récursive qui se termine par une StackOverflowException.
La solution la plus simple consiste à ne pas inclure la collection getChildren dans les méthodes.
equals()
. Si un savant fou créait une copie de moi, nous serions équivalents. Mais nous n'aurions pas le même père.