Quels problèmes doivent être pris en compte lorsque vous remplacez equals et hashCode en Java?


Quels problèmes / pièges doivent être pris en compte lorsque vous remplacez equals et hashCode?

Author: George Stocker, 2008-08-26

11 answers

La théorie (pour les juristes de la langue et les mathématiquement inclinés):

equals() (javadoc) doit définir une relation d'équivalence (il doit être réflexive, symétrique, et transitive). De plus, il doit être cohérent (si les objets ne sont pas modifiés, il doit continuer à renvoyer la même valeur). De plus, o.equals(null) doit toujours renvoyer false.

hashCode() (javadoc) doivent également être cohérente (si l' l'objet n'est pas modifié en termes de equals(), il doit continuer à renvoyer la même valeur).

Le rapport entre les deux méthodes est:

Chaque fois que a.equals(b), alors a.hashCode() doit être identique à b.hashCode().

En pratique:

Si vous remplacez l'un, vous devez remplacer l'autre.

Utiliser le même ensemble de champs que vous utilisez pour calculer equals() pour calculer hashCode().

Utilisez les excellentes classes d'aideEqualsBuilder et Il s'agit de la première version 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();
    }
}

Rappelez-vous Aussi:

Lors de l'utilisation d'une collection basée sur le hachage ou Map telle que HashSet, Ensemble de boutons, HashMap, Hashtable , ou WeakHashMap, assurez-vous que le hashCode() des objets clés que vous mettez dans la collection ne change jamais pendant que l'objet est dans la collection. La manière à l'épreuve des balles d'assurer c'est pour rendre vos clés immuables, ce qui a aussi d'autres avantages.

 1380
Author: Antti Sykäri, 2015-07-29 19:23:43

Il y a des problèmes qui méritent d'être remarqués si vous avez affaire à des classes persistantes à l'aide d'un Mappeur de relation objet (ORM) comme Hibernate, si vous ne pensiez pas que cela était déjà déraisonnablement compliqué!

Les objets chargés paresseux sont des sous-classes

Si vos objets sont conservé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 depuis le 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() 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 avez affaire à un ORM, l'utilisation de o instanceof Person est la seule chose qui se comportera correctement.

Les objets chargés paresseux ont des champs null

Les ORM utilisent généralement les getters pour forcer le chargement d'objets chargés paresseux. Cela signifie que person.name sera null si person est lazy loaded, même si person.getName() force le chargement et renvoie "John Doe". D'après mon expérience, cela apparaît plus souvent dans hashCode() et equals().

Si vous avez affaire à un ORM, assurez-vous de toujours utiliser des getters, et jamais de références de champ dans hashCode() et equals().

L'enregistrement d'un objet changera son état

Les objets persistants utilisent souvent un champ id pour contenir la clé de l'objet. Ce champ sera automatiquement mis à jour lorsqu'un objet est d'abord enregistré. N'utilisez pas de champ id dans hashCode(). Mais vous pouvez l'utiliser dans equals().

Modèle que j'utilise souvent est

if (this.getId() == null) {
    return this == other;
}
else {
    return this.getId().equals(other.getId());
}

Mais: vous ne peut pas inclure getId() dans hashCode(). Si vous le faites, lorsqu'un objet est persisté, son hashCode change. Si l'objet est dans un HashSet, vous ne le retrouverez "jamais".

Mon Person exemple, je serais probablement utiliser getName() pour hashCode et getId() plus getName() (juste pour de la paranoïa) de equals(). C'est correct s'il y a un risque de "collisions" pour hashCode(), mais jamais correct pour equals().

hashCode() doit utiliser le non-changement de sous-ensemble de propriétés de equals()

 281
Author: Johannes Brodwall, 2017-01-26 11:00:31

Une clarification sur le obj.getClass() != getClass().

Cette déclaration est le résultat de equals() étant hostile à l'héritage. Le JLS (Java language specification) spécifie que si A.equals(B) == true alors B.equals(A) doit également retourner true. Si vous omettez cette instruction, les classes héritées qui remplacent equals() (et modifient son comportement) briseront cette spécification.

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, new B(1,1).equals(new B(1,1)) résultat de donner 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;

, Évidemment, 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 B instance, mais vérifiez après pour A instance:

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 sortira:

a.equals(b) == true;
b.equals(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 classe A (parce que vous l'étendez), dans ce cas, vous appelez super.equals() aussi .

 81
Author: Ran Biron, 2014-08-12 19:54:24

Pour une implémentation adaptée à l'héritage, consultez la solution de Tal Cohen, Comment implémenter correctement la méthode equals ()?

Résumé:

Dans son livreEffective 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 non symétrique blindlyEquals () dans les deux sens. blindlyEquals() est remplacé par des 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 (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.

 42
Author: Kevin Wong, 2012-08-30 09:12:57

Toujours étonné qu'aucun n'ait recommandé la bibliothèque 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());
    }
 31
Author: Eugene, 2013-12-17 19:42:59

Il existe deux méthodes dans la super classe en tant que java.lang.Objet. 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, cependant 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 obtenir plus, veuillez vérifier ce lien comme 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

Amusez-vous! @.@

 25
Author: Luna Kong, 2013-12-20 06:14:09

Il existe plusieurs façons de vérifier l'égalité de classe avant de vérifier l'égalité des membres, et je pense que les deux sont utiles dans les bonnes circonstances.

  1. Utilisez l'opérateur instanceof.
  2. Utilisez this.getClass().equals(that.getClass()).

J'utilise #1 dans une implémentation final equals, ou lors de l'implémentation d'une interface qui prescrit un algorithme pour equals (comme les interfaces de collection java.util-la bonne façon de vérifier avec (obj instanceof Set) ou quelle que soit l'interface que vous implémentez). Il est généralement un mauvais choix lorsque equals peut être remplacé car cela brise la propriété de symétrie.

L'option #2 permet d'étendre la classe en toute sécurité sans surcharger les égaux ou briser la symétrie.

Si votre classe est également Comparable, le equals et compareTo méthodes devraient être compatibles trop. Voici un modèle pour la méthode equals dans une classe Comparable:

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;
  }

}
 18
Author: erickson, 2008-08-29 16:15:27

Pour les égaux, regardez dans Secrets des Égaux parAngelika Langer . Je l'aime beaucoup. Elle est aussi une excellente FAQ sur 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 "comparaison de types mixtes". Amusez-vous à les lire!

 15
Author: Johannes Schaub - litb, 2009-02-27 22:05:04

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. Lorsque 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 doivent être prises 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é. Alors la méthode equals () peut être remplacer.

Nous devrions toujours remplacer la méthode hashCode() chaque fois que nous remplaçons la méthode equals (). Si non, quelles seront les conséquences? 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 renvoie il.

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
 11
Author: rohan kamat, 2013-11-13 21:27:49

Logiquement, nous avons:

a.getClass().equals(b.getClass()) && a.equals(b)a.hashCode() == b.hashCode()

, Mais pas vice-versa!

 7
Author: Khaled.K, 2013-08-09 15:41:14

Un gotcha que j'ai trouvé est où deux objets 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 lorsque vous faites des mappages Hibernate par exemple.

Si vous incluez les deux extrémités de la relation dans vos tests hashCode ou equals, il est possible d'entrer dans une boucle récursive qui se termine par une StackOverflowException.
La solution la plus simple est de ne pas inclure la collection getChildren dans les méthodes.

 6
Author: Darren Greaves, 2008-09-02 21:06:47