Question Quels problèmes devraient être considérés en remplaçant equals et hashCode en Java?


Quels problèmes / pièges doivent être pris en compte lors du dépassement equals et hashCode?


617
2017-08-26 08:50


origine


Réponses:


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

equals() (javadoc) doit définir une relation d'équivalence (elle doit être réfléchi, symétrique, et transitif). En outre, il doit être cohérent (Si les objets ne sont pas modifiés, alors il doit continuer à retourner 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 à retourner la même valeur).

le relation entre les deux méthodes est:

N'importe quand a.equals(b), puis a.hashCode() doit être le même que b.hashCode().

En pratique:

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

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

Utilisez les excellentes classes d'aide EqualsBuilder et HashCodeBuilder du Apache Commons Lang bibliothèque. 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'un hachage Collection ou Carte tel que HashSet, LinkedHashSet, HashMap, Hashtable, ou WeakHashMap, assurez-vous que le hashCode () des objets clés que vous avez placés dans la collection ne change jamais pendant que l'objet est dans la collection. Le moyen à l'épreuve des balles est de rendre vos clés immuables, qui a aussi d'autres avantages.


1370
2017-11-02 02:58



Il y a quelques problèmes à noter si vous traitez avec des classes qui sont persistées en utilisant un Object-Relationship Mapper (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 conservés à l'aide d'un ORM, vous devrez dans de nombreux cas traiter des proxys dynamiques pour éviter de charger l'objet 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 quethis.getClass() == o.getClass() 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 traitez avec un ORM, en utilisant o instanceof Person est la seule chose qui va se comporter correctement.

Les objets chargés paresseux ont des champs nuls

Les ORM utilisent généralement les getters pour forcer le chargement des objets chargés paresseux. Cela signifie que person.name sera null si person est paresseux chargé, même si person.getName() forces de chargement et retourne "John Doe". D'après mon expérience, cela se produit plus souvent hashCode() et equals().

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

Enregistrer un objet va changer 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 d'identifiant dans hashCode(). Mais vous pouvez l'utiliser dans 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 objet est conservé, son hashCode changements. Si l'objet est dans un HashSet, vous ne le "retrouverez" jamais.

Dans mon Person Par 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" pour hashCode(), mais jamais d'accord pour equals().

hashCode() devrait utiliser le sous-ensemble non changeant de propriétés de equals()


281
2017-08-28 13:16



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

Cette déclaration est le résultat de equals() être l'héritage hostile. La JLS (spécification de langage Java) spécifie que si A.equals(B) == true puis B.equals(A) doit aussi retourner true. Si vous omettez cette instruction héritant des classes qui ont priorité equals() (et changer son comportement) va briser cette spécification.

Considérez 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 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 l'appel du principe de substitution de Liskov super.equals(other) non seulement dans le cas de B par exemple, mais vérifiez après pour 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;

Ce qui va sortir:

a.equals(b) == true;
b.equals(a) == true;

Où, si a n'est pas une référence de B, alors ça pourrait être une référence de classe A (parce que vous l'étendez), dans ce cas, vous appelez super.equals()  aussi.


79
2017-09-11 03:06



Pour une mise en œuvre favorable à l'héritage, consultez la solution de Tal Cohen, Comment puis-je implémenter correctement la méthode equals ()?

Résumé:

Dans son livre Guide du langage de programmation Java efficace (Addison-Wesley, 2001), Joshua Bloch affirme que "Il n'y a tout simplement aucun moyen d'étendre une classe instanciable et d'ajouter un aspect tout en préservant le contrat d'égal à é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 surchargé par les sous-classes, equals () est hérité et ne l'est jamais.

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 entre les hiérarchies d'héritage si le Principe de substitution de Liskov doit être satisfait.


41
2018-02-12 07:17



Toujours étonné que personne n'a recommandé 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());
    }

31
2017-12-20 06:14



Il existe deux méthodes dans 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 vérifier ce lien 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! @. @


25
2017-08-28 18:25



Il y a 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.

  1. Utilisez le instanceof opérateur.
  2. Utilisation this.getClass().equals(that.getClass()).

J'utilise # 1 dans un final égale à la mise en œuvre, ou lors de la mise en œuvre d'une interface qui prescrit un algorithme pour égal (comme le 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 quand les égales peuvent être remplacées parce que cela casse la propriété de symétrie.

L'option # 2 permet d'étendre la classe en toute sécurité sans écraser ou symétriser la symétrie.

Si votre classe est aussi Comparable, la equals et compareTo les méthodes doivent être cohérentes aussi. Voici un modèle pour la méthode égale dans un 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;
  }

}

18
2018-02-27 22:05



Pour les égaux, regardez dans Les secrets des égaux par Angelika 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 aussi avec Part-2 et "comparaison de type mixte". Amusez-vous à les lire!


15
2017-10-24 10:56



La méthode equals () est utilisée pour déterminer l'égalité de deux objets.

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 considérées. 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é. Ensuite, la méthode equals () peut être surchargée.

nous devrions toujours remplacer la méthode hashCode () chaque fois que nous redéfinissons la méthode equals (). Si non, que va-t-il se passer? Si nous utilisons des tables de hachage dans notre application, il ne se comportera pas comme prévu. Comme le hashCode est utilisé pour déterminer l'égalité des valeurs stockées, il ne retournera pas la bonne valeur correspondante pour une clé.

L'implémentation par défaut donnée est la méthode hashCode () de la classe Object qui 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

11
2018-03-24 14:34



Logiquement, nous avons:

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

Mais ne pas vice versa!


7
2017-09-02 21:06