617 votes

La substitution d’equals et hashCode en Java

Quels sont les enjeux / pièges doivent être envisagées lors de la substitution de et ?

1435voto

Antti Sykäri Points 10381

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.

295voto

Johannes Brodwall Points 3469

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

116voto

Alotor Points 3438

Si vous utilisez Eclipse, il a intégré un très cool / Générateur.

Vous avez juste à être sur une classe et à faire : faites un clic droit > code Source > générer hashCode() et `` ...

Ensuite, une fenêtre apparaîtra et vous pouvez choisir les champs à inclure dans vos méthodes.

85voto

Ran Biron Points 3015

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 ().

84voto

WMR Points 5869

Je vous parle juste aux points 7 et 8 dans Josh Blochs excellent livre « Java efficace », il a tous les pièges et les embûches que vous devez savoir. Le chapitre pertinent est même disponible en ligne

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X