Quels sont les enjeux / pièges doivent être envisagées lors de la substitution de et
?
Réponses
Trop de publicités?La théorie (pour la langue des avocats et des mathématiquement incliné):
equals()
(javadoc) doit définir une relation d'égalité (il doit être réflexive, symétriqueet transitive). En outre, il doit être cohérent (si les objets ne sont pas modifiées, alors elle doit retourner la même valeur). En outre, o.equals(null)
doit toujours retourner false.
hashCode()
(javadoc) doit également être cohérente (si l'objet n'est pas modifié en termes de equals()
, elle doit retourner la même valeur).
La relation entre les deux méthodes est:
Chaque fois qu' a.equals(b)
, alors a.hashCode()
doit être la même que b.hashCode()
.
Dans la pratique:
Si vous remplacez un, alors vous devez remplacer l'autre.
Utiliser le même ensemble de champs que vous utilisez pour calculer equals()
pour calculer hashCode()
.
Utiliser l'excellent helper classes EqualsBuilder et HashCodeBuilder de l' Apache Commons Lang de la bibliothèque. Un exemple:
public class Person {
private String name;
private int age;
// ...
public int hashCode() {
return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers
// if deriving: appendSuper(super.hashCode()).
append(name).
append(age).
toHashCode();
}
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();
}
}
Rappelez-vous aussi:
Lors de l'utilisation d'une base de hachage de la Collection ou de la Carte comme HashSet, LinkedHashSet, HashMap, table de hachage, ou WeakHashMap, assurez-vous que le hashCode() de la touche objets que vous mettez dans la collection ne change jamais, alors que l'objet est dans la collection. Le pare-balles moyen de s'en assurer est de faire de vos clés immuable, qui a également d'autres avantages.
Il y a quelques problèmes intéressant de remarquer que si vous faites affaire avec des classes qui sont conservées à l'aide d'un Objet-Relation Mapper (ORM comme Hibernate. Si vous ne pensiez pas que c'était excessivement compliqué déjà!
Paresseux objets chargés sont des sous-classes
Si vos objets sont conservés en utilisant un ORM, dans de nombreux cas, vous aurez affaire à des proxies dynamiques éviter le chargement d'objet trop tôt à partir de la banque de données. Ces procurations sont implémentées comme des sous-classes de votre propre classe. Cela signifie qu'this.getClass() == o.getClass()
retournera 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 faites affaire avec un ORM à l'aide de o instanceof Person
est la seule chose qui va se comporter correctement.
Paresseux objets chargés null-champs
Orm généralement utiliser les accesseurs pour forcer le chargement de paresseux objets chargés. Cela signifie qu' person.name
sera nulle si l' person
n'est pas chargé, même si person.getName()
des forces de chargement et retours "John Doe". Dans mon expérience, cela revient le plus souvent dans hashCode
et equals
.
Si vous faites affaire avec un ORM, assurez-vous de toujours utiliser les méthodes de lecture, et de ne jamais les références de champ en hashCode
et equals
.
La sauvegarde d'un objet va changer son état
Les objets persistants souvent utiliser un id
champ de détenir la clé de l'objet. Ce champ sera automatiquement mis à jour lorsqu'un objet est d'abord enregistré. Ne pas utiliser un champ id en hashCode
. Mais vous pouvez l'utiliser en equals
.
Un modèle que j'utilise souvent
if (this.getId() == null) {
return this == other;
} else {
return this.getId() == other.getId();
}
Mais: Vous ne pouvez pas inclure getId()
en hashCode()
. Si vous le faites, lorsqu'un objet est persistant, c'est hashCode
changements. Si l'objet est dans un HashSet
, vous aurez la "jamais" de le retrouver.
Dans mon Person
exemple, je serais probablement utiliser getName()
pour hashCode
et getId
plus getName()
(juste pour de la paranoïa) equals
. C'est pas grave si il y a un risque de "collisions" pour hashCode
, mais jamais acceptable pour equals
.
hashCode
devez utiliser le non-changement de sous-ensemble de propriétés à partir d' equals
Une clarification au sujet de la "obj.getClass() != getClass()".
Cette déclaration est le résultat de equals() étant l'héritage hostile. Le JLS (Java language specification) précise que si A. equals(B)==vrai alors B. equals(A) doit retourner la valeur true. Si vous omettez cette déclaration d'hériter de classes qui remplacent equals() (et modifier son comportement) rompre cette spécification.
Considérons l'exemple suivant de ce qui se passe lorsque l'instruction 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 nouvelles d'Un(1).est égal à(new A(1)) Aussi, new B(1,1).equals(new B(1,1)) donne vrai, comme il se doit.
Cela ressemble très bonne, mais regardez ce qui se passe si vous essayez d'utiliser les deux classes:
A a = new A(1);
B b = new B(1,1);
a.equals(b) == true;
b.equals(a) == false;
Évidemment, c'est faux.
Si vous voulez vous assurer que la condition symétrique. a=b si b=a, et le principe de substitution de Liskov appel super.equals(other)
non seulement dans le cas de B, mais vérifier après pour Un 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 sera de sortie: un.equals(b) == true; b.est égal à(a) == true;
Où, si a n'est pas une référence de B, alors il peut être une être une référence de la classe A (parce que vous l'étendre), dans ce cas vous l'appeler super.equals ().