web-dev-qa-db-fra.com

Meilleure implémentation de la méthode hashCode pour une collection

Comment décider de la meilleure implémentation de la méthode hashCode() pour une collection (en supposant que la méthode equals a été remplacée correctement)?

288
Omnipotent

La meilleure implémentation? C'est une question difficile car cela dépend du modèle d'utilisation.

Une bonne mise en œuvre raisonnable a été proposée dans presque tous les cas dans de Josh Bloch Effective Java dans la rubrique 8 (deuxième édition). La meilleure chose à faire est de regarder là-bas, car l'auteur explique pourquoi l'approche est bonne.

Une version courte

  1. Créez un int result et attribuez une valeur différente de zéro.

  2. Pour chaque champ f testé dans la méthode equals(), calcule un code de hachage c en:

    • Si le champ f est un boolean: calculer (f ? 0 : 1);
    • Si le champ f est un byte, char, short ou int: calcule (int)f;
    • Si le champ f est un long: calculer (int)(f ^ (f >>> 32));
    • Si le champ f est un float: Calculate Float.floatToIntBits(f);
    • Si le champ f est un double: calculez Double.doubleToLongBits(f) et gérez la valeur de retour comme chaque valeur longue;
    • Si le champ f est un objet : utilisez le résultat de la méthode hashCode() ou 0 si f == null;
    • Si le champ f est un tableau : affichez chaque champ en tant qu'élément distinct et calculez la valeur de hachage de manière récursive . et combinez les valeurs comme décrit ci-après.
  3. Combinez la valeur de hachage c avec result:

    result = 37 * result + c
    
  4. Retourne result

Cela devrait entraîner une distribution appropriée des valeurs de hachage pour la plupart des situations d'utilisation.

426
dmeister

Si vous êtes satisfait de la mise en œuvre Effective Java recommandée par dmeister, vous pouvez utiliser un appel de bibliothèque au lieu de lancer le vôtre:

@Override
public int hashCode() {
    return Objects.hashCode(this.firstName, this.lastName);
}

Cela nécessite soit Goyave (com.google.common.base.Objects.hashCode) ou la bibliothèque standard dans Java 7 (Java.util.Objects.hash), mais fonctionne de la même manière.

133
bacar

Il est préférable d’utiliser la fonctionnalité fournie par Eclipse, qui fait un très bon travail, et vous pouvez consacrer vos efforts et votre énergie au développement de la logique commerciale.

58
Warrior

Bien que cela soit lié à Android documentation (Wayback Machine) et Mon propre code sur Github , cela fonctionnera pour Java en général. Ma réponse est une extension de Answer de dmeister avec juste un code beaucoup plus facile à lire et à comprendre.

@Override 
public int hashCode() {

    // Start with a non-zero constant. Prime is preferred
    int result = 17;

    // Include a hash for each field.

    // Primatives

    result = 31 * result + (booleanField ? 1 : 0);                   // 1 bit   » 32-bit

    result = 31 * result + byteField;                                // 8 bits  » 32-bit 
    result = 31 * result + charField;                                // 16 bits » 32-bit
    result = 31 * result + shortField;                               // 16 bits » 32-bit
    result = 31 * result + intField;                                 // 32 bits » 32-bit

    result = 31 * result + (int)(longField ^ (longField >>> 32));    // 64 bits » 32-bit

    result = 31 * result + Float.floatToIntBits(floatField);         // 32 bits » 32-bit

    long doubleFieldBits = Double.doubleToLongBits(doubleField);     // 64 bits (double) » 64-bit (long) » 32-bit (int)
    result = 31 * result + (int)(doubleFieldBits ^ (doubleFieldBits >>> 32));

    // Objects

    result = 31 * result + Arrays.hashCode(arrayField);              // var bits » 32-bit

    result = 31 * result + referenceField.hashCode();                // var bits » 32-bit (non-nullable)   
    result = 31 * result +                                           // var bits » 32-bit (nullable)   
        (nullableReferenceField == null
            ? 0
            : nullableReferenceField.hashCode());

    return result;

}

EDIT

Généralement, lorsque vous remplacez hashcode(...), vous souhaitez également remplacer equals(...). Donc, pour ceux qui vont ou ont déjà implémenté equals, voici une bonne référence de mon Github ...

@Override
public boolean equals(Object o) {

    // Optimization (not required).
    if (this == o) {
        return true;
    }

    // Return false if the other object has the wrong type, interface, or is null.
    if (!(o instanceof MyType)) {
        return false;
    }

    MyType lhs = (MyType) o; // lhs means "left hand side"

            // Primitive fields
    return     booleanField == lhs.booleanField
            && byteField    == lhs.byteField
            && charField    == lhs.charField
            && shortField   == lhs.shortField
            && intField     == lhs.intField
            && longField    == lhs.longField
            && floatField   == lhs.floatField
            && doubleField  == lhs.doubleField

            // Arrays

            && Arrays.equals(arrayField, lhs.arrayField)

            // Objects

            && referenceField.equals(lhs.referenceField)
            && (nullableReferenceField == null
                        ? lhs.nullableReferenceField == null
                        : nullableReferenceField.equals(lhs.nullableReferenceField));
}
55

Premièrement, assurez-vous que Equals est correctement implémenté. De n article IBM DeveloperWorks :

  • Symétrie: pour deux références, a et b, a.equals (b) si et seulement si b.equals (a)
  • Réflexivité: pour toutes les références non NULL, a.equals (a)
  • Transitivité: Si a.equals (b) et b.equals (c), alors a.equals (c)

Assurez-vous ensuite que leur relation avec hashCode respecte le contact (du même article):

  • Cohérence avec hashCode (): deux objets identiques doivent avoir la même valeur hashCode ()

Enfin, une bonne fonction de hachage doit s’efforcer d’approcher le fonction de hachage idéale .

17
Grey Panther

à propos de.blogspot.com, vous avez dit

si equals () renvoie true pour deux objets, alors hashCode () doit renvoyer la même valeur. Si equals () renvoie false, alors hashCode () devrait renvoyer des valeurs différentes

Je ne peux pas être d'accord avec toi. Si deux objets ont le même hashcode, cela ne signifie pas nécessairement qu'ils sont égaux.

Si A est égal à B, alors A.hashcode doit être égal à B.hascode

mais

si A.hashcode est égal à B.hascode, cela ne signifie pas que A doit être égal à B

11
panzupa

Il existe une bonne implémentation de la logique hashcode() et equals() de Effective Java dans Apache Commons Lang . Commander HashCodeBuilder et EqualsBuilder .

7
Rudi Adianto

Si vous utilisez Eclipse, vous pouvez générer equals() et hashCode() en utilisant:

Source -> Générer hashCode () et equals ().

En utilisant cette fonction, vous pouvez choisir quels champs vous voulez utiliser pour l’égalité et le calcul du code de hachage, et Eclipse génère les méthodes correspondantes.

7
Johannes K. Lehnert

Juste une petite note pour compléter une autre réponse plus détaillée (en terme de code):

Si je considère la question comment faire-je-créer-une-table de hachage en Java et en particulier le jGuru FAQ entrée , je crois que certains autres critères sur lesquels un code de hachage pourrait être jugé sont:

  • synchronisation (algo supporte-t-il ou non les accès concurrents)?
  • fail safe iteration (l'algo détecte-t-il une collection qui change au cours de l'itération)
  • valeur nulle (le code de hachage prend-il en charge la valeur nulle dans la collection)
5
VonC

Si je comprends bien votre question, vous avez une classe de collection personnalisée (c'est-à-dire une nouvelle classe qui s'étend à partir de l'interface Collection) et vous souhaitez implémenter la méthode hashCode ().

Si votre classe de collection étend AbstractList, vous n'avez pas à vous en soucier, il existe déjà une implémentation de equals () et hashCode () qui fonctionne en itérant à travers tous les objets et en ajoutant leur hashCodes () ensemble.

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

Maintenant, si ce que vous voulez est le meilleur moyen de calculer le code de hachage pour une classe spécifique, j'utilise normalement l'opérateur ^ (exclusivité au bit ou) au traitement de tous les champs que j'utilise dans la méthode equals:

public int hashCode(){
   return intMember ^ (stringField != null ? stringField.hashCode() : 0);
}
4
Mario Ortegón

@ about8: il y a un bug assez sérieux.

Zam obj1 = new Zam("foo", "bar", "baz");
Zam obj2 = new Zam("fo", "obar", "baz");

même hashcode

vous voulez probablement quelque chose comme

public int hashCode() {
    return (getFoo().hashCode() + getBar().hashCode()).toString().hashCode();

(pouvez-vous obtenir hashCode directement à partir de int dans Java ces jours-ci? Je pense que cela fait de l'autodécastillage .. si c'est le cas, sautez le toString, c'est moche.)

2
SquareCog

Comme vous avez spécifiquement demandé des collections, j'aimerais ajouter un aspect que les autres réponses n'ont pas encore mentionné: une carte de hachage ne s'attend pas à ce que leurs clés changent leur code de hachage une fois ajoutées à la collection. Serait vaincre le but entier ...

2
Olaf Kock

Utilisez les méthodes de réflexion sur Apache Commons EqualsBuilder et HashCodeBuilder .

2
Vihung

J'utilise un petit wrapper autour de Arrays.deepHashCode(...) car il gère correctement les tableaux fournis en tant que paramètres

public static int hash(final Object... objects) {
    return Arrays.deepHashCode(objects);
}
2
starikoff

L'implémentation standard est faible et son utilisation entraîne des collisions inutiles. Imaginez un

class ListPair {
    List<Integer> first;
    List<Integer> second;

    ListPair(List<Integer> first, List<Integer> second) {
        this.first = first;
        this.second = second;
    }

    public int hashCode() {
        return Objects.hashCode(first, second);
    }

    ...
}

Maintenant,

new ListPair(List.of(a), List.of(b, c))

et

new ListPair(List.of(b), List.of(a, c))

ont la même hashCode, à savoir 31*(a+b) + c que le multiplicateur utilisé pour List.hashCode est réutilisé ici. De toute évidence, les collisions sont inévitables, mais produire des collisions inutiles est tout simplement… inutile.

L'utilisation de 31 n'a rien de vraiment intelligent. Le multiplicateur doit être impair pour éviter de perdre des informations (tout multiplicateur pair perd au moins le bit le plus significatif, les multiples de quatre en perdent deux, etc.). Tout multiplicateur impair est utilisable. De petits multiplicateurs peuvent conduire à un calcul plus rapide (le JIT peut utiliser des décalages et des ajouts), mais étant donné que la multiplication n'a qu'une latence de trois cycles sur les processeurs Intel/AMD modernes, cela n'a guère d'importance. De petits multiplicateurs entraînent également plus de collision pour de petites entrées, ce qui peut parfois poser problème.

L'utilisation d'un nombre premier est inutile car les nombres premiers n'ont aucune signification dans l'anneau Z/(2 ** 32).

Donc, je vous recommanderais d’utiliser un grand nombre impair choisi au hasard (n'hésitez pas à prendre une prime). Comme les processeurs i86/AMD64 peuvent utiliser une instruction plus courte pour les opérandes dans un seul octet signé, les multiplicateurs tels que 109 ont un avantage minime sur le plan de la vitesse. Pour réduire le nombre de collisions, prenez quelque chose comme 0x58a54cf5.

Utiliser différents multiplicateurs dans différents endroits est utile, mais probablement pas suffisant pour justifier le travail supplémentaire.

1
maaartinus

toute méthode de hachage qui répartit uniformément la valeur de hachage sur la plage possible est une bonne implémentation. Voir Effective Java ( http://books.google.com.au/books?id=ZZOiqZQIbRMC&dq=effective+Java&pg=PP1&ots=UZMZ2siN25&sig=kR0n73DHJOn-D77qGJ0wZHa 1 & ct = result ), il existe un bon conseil pour la mise en oeuvre du hashcode (élément 9 je pense ...).

1
Chii

Voici une autre démonstration de l’approche JDK 1.7+ avec les logiques de la superclasse comptabilisées. Je le vois comme très pratique avec la classe Object hashCode (), une dépendance JDK pure et aucun travail manuel supplémentaire. Veuillez noter que Objects.hash() est null tolérant.

Je n'ai inclus aucune implémentation de equals(), mais en réalité, vous en aurez bien sûr besoin.

import Java.util.Objects;

public class Demo {

    public static class A {

        private final String param1;

        public A(final String param1) {
            this.param1 = param1;
        }

        @Override
        public int hashCode() {
            return Objects.hash(
                super.hashCode(),
                this.param1);
        }

    }

    public static class B extends A {

        private final String param2;
        private final String param3;

        public B(
            final String param1,
            final String param2,
            final String param3) {

            super(param1);
            this.param2 = param2;
            this.param3 = param3;
        }

        @Override
        public final int hashCode() {
            return Objects.hash(
                super.hashCode(),
                this.param2,
                this.param3);
        }
    }

    public static void main(String [] args) {

        A a = new A("A");
        B b = new B("A", "B", "C");

        System.out.println("A: " + a.hashCode());
        System.out.println("B: " + b.hashCode());
    }

}
1
Roman Nikitchenko

Je préfère utiliser les méthodes utilitaires fromm librairies Google Collections de la classe Objects qui m'aident à garder mon code propre. Très souvent, les méthodes equals et hashcode sont créées à partir du modèle IDE, elles ne sont donc pas propres à la lecture.

1
panzupa

Lors de la combinaison de valeurs de hachage, j'utilise généralement la méthode de combinaison utilisée dans la bibliothèque boost c ++, à savoir:

seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);

Cela permet assez bien d’assurer une distribution uniforme. Pour en savoir plus sur le fonctionnement de cette formule, voir l'article StackOverflow: nombre magique dans boost :: hash_combine

Il existe une bonne discussion sur les différentes fonctions de hachage à l'adresse suivante: http://burtleburtle.net/bob/hash/doobs.html

0
Edward Loper