web-dev-qa-db-fra.com

La fonction hashCode générée par Eclipse est-elle utile?

Le menu source Eclipse a une méthode "generate hashCode/equals" qui génère des fonctions comme celle ci-dessous.

String name; 
@Override
public int hashCode()
{
    final int prime = 31;
    int result = 1;
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    return result;
}

@Override
public boolean equals(Object obj)
{
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    CompanyRole other = (CompanyRole) obj;
    if (name == null)
    {
        if (other.name != null)
            return false;
    } else if (!name.equals(other.name))
        return false;
    return true;
}

Si je sélectionne plusieurs champs lors de la génération de hashCode() et equals(), Eclipse utilise le même schéma que celui présenté ci-dessus.

Je ne suis pas un expert des fonctions de hachage et j'aimerais savoir comment "bien" la fonction de hachage générée est? Quelles sont les situations où il va tomber en panne et causer trop de collisions?

31
ams

Vous pouvez voir l’implémentation de la fonction hashCode dans Java.util.ArrayList en tant que 

public int hashCode() {
    int hashCode = 1;
    Iterator<E> i = iterator();
    while (i.hasNext()) {
        E obj = i.next();
        hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
    }
    return hashCode;
}

C'est un exemple et votre code généré par Eclipse suit une méthode similaire pour le mettre en œuvre. Mais si vous sentez que vous devez implémenter votre hashCode par vous-même, Joshua Bloch donne quelques bonnes directives dans son célèbre livre Effective Java . Je posterai ces points importants du point 9 de ce livre. Ce sont,

  1. Stockez une valeur constante non nulle, par exemple 17, dans une variable int appelée résultat.
  2. Pour chaque champ significatif f dans votre objet (chaque champ pris en compte par la méthode equals, c'est-à-dire), procédez comme suit: 

    une. Calculez un code de hachage int pour le champ:

    je. Si le champ est un booléen, calculez (f? 1: 0). 

    ii. Si le champ est un octet, char, short ou int, calculez (int) f. 

    iii. Si le champ est long, calculez (int) (f ^ (f >>> 32)). 

    iv. Si le champ est un float, calculez Float.floatToIntBits (f). 

    v. Si le champ est un double, calculez Double.doubleToLongBits (f), puis hachez le résultat obtenu comme dans l'étape 2.a.iii. 

    vi. Si le champ est une référence d’objet et que la méthode equals de cette classe compare le champ en invoquant récursivement égaux, invoque récursivement hashCode sur le champ. Si une comparaison plus complexe est requise, calculez une «représentation canonique» pour ce champ et appelez hashCode sur la représentation canonique. Si la valeur du champ est null, retourne 0 (ou une autre constante, mais 0 est traditionnelle)

    vii. Si le champ est un tableau, traitez-le comme si chaque élément était un champ séparé. C'est-à-dire, calculez un code de hachage pour chaque élément significatif en appliquant Ces règles de manière récursive étape 2.b. Si chaque élément D'un champ de tableau est significatif, vous pouvez utiliser l'une des méthodes Arrays.hashCode ajoutées à la version 1.5.

    b. Combinez le code de hachage c calculé à l'étape 2.a dans le résultat comme suit:

       result = 31 * result + c;
    
  3. Retourne le résultat.

  4. Lorsque vous avez terminé d'écrire la méthode hashCode, demandez-vous si Instances égales ont des codes de hachage égaux. Ecrivez des tests unitaires pour vérifier votre intuition! Si des instances égales ont des codes de hachage inégaux, déterminez pourquoi et corrigez le problème.

Les concepteurs de langage Java et Eclipse semblent suivre des directives similaires, je suppose. Bonne codage. À votre santé.

16
sakthisundar

Depuis Java 7, vous pouvez utiliser Java.util.Objects pour écrire des méthodes courtes et élégantes:

class Foo {
  private String name;
  private String id;

  @Override
  public int hashCode() {
    return Objects.hash(name,id);
  }

  @Override
  public boolean equals(Object obj) {
    if (obj instanceof Foo) {
      Foo right = (Foo) obj;
      return Objects.equals(name,right.name) && Objects.equals(id,right.id);
    }
    return false;
  }
}
12
leftbit

Généralement c'est bon, mais:

  1. La goyave le fait en quelque sorte mieux , je le préfère. [EDIT: Il semble qu’à partir de JDK7, Java fournisse une fonction de hachage similaire].
  2. Certains frameworks peuvent poser des problèmes lors de l'accès direct aux champs au lieu d'utiliser des setters/getters, comme Hibernate par exemple. Pour certains champs créés par paresseux par Hibernate, il crée un proxy et non le véritable objet. Hibernate ne fait qu'appeler le getter pour obtenir la valeur réelle dans la base de données.
5
Eugene

Oui, c'est parfait :) Vous verrez cette approche presque partout dans le code source Java.

4
Martijn Courteaux

Si vous utilisez Apache Software Foundation (bibliothèque commons-lang), les classes Ci-dessous vous aideront à générer des méthodes hashcode/equals/toString à l'aide de la réflexion.

Vous n'avez pas à vous soucier de la régénération des méthodes hashcode/equals/toString lorsque vous ajoutez/supprimez des variables d'instance.

EqualsBuilder - Cette classe fournit des méthodes pour construire une bonne méthode égale pour toute classe. Il suit les règles énoncées dans Effective Java, par Joshua Bloch. En particulier, la règle de comparaison des doublons, des flottants et des tableaux peut être délicate. En outre, il peut être difficile d’assurer la cohérence de equals () et hashCode ().

HashCodeBuilder - Cette classe permet de construire une bonne méthode hashCode pour toute classe. Il suit les règles énoncées dans le livre Effective Java de Joshua Bloch. Écrire une bonne méthode hashCode est en fait assez difficile. Ce cours vise à simplifier le processus.

ReflectionToStringBuilder - Cette classe utilise la réflexion pour déterminer les champs à ajouter. Étant donné que ces champs sont généralement privés, la classe utilise AccessibleObject.setAccessible (Java.lang.reflect.AccessibleObject [], boolean) pour modifier la visibilité des champs. Cela échouera sous un gestionnaire de sécurité, à moins que les autorisations appropriées ne soient correctement configurées.

Dépendance Maven:

<dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
        <version>${commons.lang.version}</version>
</dependency>

Exemple de code:

import org.Apache.commons.lang.builder.EqualsBuilder;
import org.Apache.commons.lang.builder.HashCodeBuilder;
import org.Apache.commons.lang.builder.ReflectionToStringBuilder;

public class Test{

    instance variables...
    ....

    getter/setter methods...
    ....

    @Override
    public String toString() {
        return ReflectionToStringBuilder.toString(this);
    }

    @Override
    public int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this);
    }

    @Override
    public boolean equals(Object obj) {
        return EqualsBuilder.reflectionEquals(this, obj);
    }
}
0
ravi

J'aimerais également ajouter une référence au point 9 de Effective Joshua Bloch, Effective Java 2nd Edition.

Voici une recette de Item 9 : ALWAYS OVERRIDE HASHCODE WHEN YOU OVERRIDE EQUALS

  1. Stockez une valeur constante non nulle, par exemple 17, dans une variable int appelée résultat.
  2. Pour chaque champ significatif f dans votre objet (chaque champ pris en compte par la méthode equals, c'est-à-dire), procédez comme suit:
    a. Compute an int hash code c for the field:            
        i.   If the field is a boolean, compute (f ? 1 : 0).
        ii.  If the field is a byte, char, short, or int, compute (int) f.
        iii. If the field is a long,compute(int)(f^(f>>>32)).
        iv.  If the field is a float, compute Float.floatToIntBits(f).
        v.   If the field is a double, compute Double.doubleToLongBits(f), and then hash the resulting long as in step 2.a.iii.
        vi.  If the field is an object reference and this class’s equals method compares the field by recursively invoking equals, recursively invoke hashCode on the field. If a more complex comparison is required, compute a “canonical representation” for this field and invoke hashCode on the canonical representation. If the value of the field is null, return 0 (or some other constant, but 0 is traditional).
        vii. If the field is an array, treat it as if each element were a separate field. That is, compute a hash code for each significant element by applying these rules recursively, and combine these values per step 2.b. If every element in an array field is significant, you can use one of the Arrays.hashCode methods added in release 1.5. 
   b. Combine the hash code c computed in step 2.a into result as follows: result = 31 * result + c;
3. Return result.
4. When you are finished writing the hashCode method, ask yourself whether equal instances have equal hash codes. Write unit tests to verify your intuition! If equal instances have unequal hash codes, figure out why and fix the problem.
0
Ashutosh Jindal

Un inconvénient potentiel est que tous les objets avec des champs nuls auront un code de hachage de 31; il pourrait donc y avoir de nombreuses collisions potentielles entre des objets qui ne contiennent que des champs nuls. Cela rendrait les recherches plus lentes dans Maps.

Cela peut se produire lorsque vous avez une Map dont le type de clé comporte plusieurs sous-classes. Par exemple, si vous aviez un HashMap<Object, Object>, vous pourriez avoir plusieurs valeurs de clé dont le code de hachage était 31. Certes, cela ne se produira pas aussi souvent. Si vous le souhaitez, vous pouvez modifier les valeurs du nombre premier au hasard en un nombre supérieur à 31 et réduire la probabilité de collision.

0
kc2001

C'est une manière standard d'écrire des fonctions de hachage. Cependant, vous pouvez l’améliorer/le simplifier si vous avez une certaine connaissance des domaines. Par exemple. vous pouvez omettre le contrôle nul, si votre classe garantit que le champ ne sera jamais nul (s'applique également à equals ()). Ou vous pouvez déléguer le code de hachage du champ si un seul champ est utilisé.

0
Heiko Schmitz