web-dev-qa-db-fra.com

Utilisez Objects.hash () ou votre propre implémentation hashCode ()?

J'ai récemment découvert la méthode Objects.hash() .

Ma première pensée a été que cela nettoie beaucoup votre implémentation de hashCode(). Voir l'exemple suivant:

@Override
//traditional
public int hashCode() {
    int hash = 5;
    hash = 67 * hash + (int)(this.id ^ (this.id >>> 32));
    hash = 67 * hash + (int)(this.timestamp ^ (this.timestamp >>> 32));
    hash = 67 * hash + Objects.hashCode(this.severity);
    hash = 67 * hash + Objects.hashCode(this.thread);
    hash = 67 * hash + Objects.hashCode(this.classPath);
    hash = 67 * hash + Objects.hashCode(this.message);
    return hash;
}

@Override
//lazy
public int hashCode() {
    return Objects.hash(id, timestamp, severity, thread, classPath, message);
}

Même si je dois dire que cela semble trop beau pour être vrai. De plus, je n'ai jamais vu cette utilisation.

Y a-t-il des inconvénients à utiliser Objects.hash() par rapport à l'implémentation de votre propre code de hachage? Quand choisirais-je chacune de ces approches?

Mise à jour

Bien que ce sujet soit marqué comme résolu, n'hésitez pas à publier des réponses qui fournissent de nouvelles informations et préoccupations.

48
Herr Derb

Notez que le paramètre de Objects.hash est Object.... Cela a deux conséquences principales:

  • Les valeurs primitives utilisées dans le calcul du code de hachage doivent être encadrées, par ex. this.id est converti de long en Long.
  • Un Object[] doit être créé pour appeler la méthode.

Le coût de création de ces objets "inutiles" peut s'additionner si hashCode est appelé fréquemment.

54
Andy Turner

Voici l'implémentation de Objects.hash - qui appelle Arrays.hashCode en interne.

public static int hash(Object... values) {
    return Arrays.hashCode(values);
}

Il s'agit de l'implémentation de la méthode Arrays.hashCode

public static int hashCode(Object a[]) {
    if (a == null)
        return 0;

    int result = 1;

    for (Object element : a)
        result = 31 * result + (element == null ? 0 : element.hashCode());

    return result;
}

Je suis donc d'accord avec @Andy Le coût de création de ces objets "inutiles" peut s'additionner si hashCode est appelé fréquemment. Si vous vous implémentez, ce serait plus rapide.

23
nagendra547

Je voudrais essayer de présenter un argument solide pour les deux.

Clause de non-responsabilité d'ouverture

Pour cette réponse, Objects.hash(), Objects.hashCode() et toute fonction fournie par une bibliothèque qui remplit ce rôle sont interchangeables. Tout d'abord, je voudrais discuter, utiliser Objects.hash() ou ne pas utiliser du tout les fonctions d'objets statiques. Tout argument pour ou contre cette méthode nécessite de faire des hypothèses sur le code compilé qui ne sont pas garanties d'être vraies. (Par exemple, l'optimiseur du compilateur peut convertir l'appel de fonction en un appel en ligne, contournant ainsi la pile d'appels supplémentaire et l'allocation d'objets. Tout comme les boucles qui ne font rien d'utile ne parviennent pas à la version compilée (sauf si vous désactivez le Vous n'avez également aucune garantie que les futures versions de Java n'incluront pas la version JVM comme C # le fait dans sa version de cette méthode. (Pour des raisons de sécurité, je crois)) Donc, le seul coffre-fort L'argument que vous pouvez faire concernant l'utilisation de cette fonction est qu'il est généralement plus sûr de laisser les détails d'un hachage approprié à cette fonction que d'essayer d'implémenter votre propre version naïve.

Pour Objects.hash

  • Garanti pour être un bon hachage.
  • Prend 5 secondes à mettre en œuvre.
  • Le vôtre aurait eu un bogue (en quelque sorte surtout si vous copiez-collez l'implémentation)

Contre Objects.hash

  • Le Java docs ne fait aucune promesse sur la compatibilité croisée de hachage (une JVM v6 et JVM v8 donneront-elles les mêmes valeurs? Toujours? À travers le système d'exploitation?)
  • La chose à propos de hashCodes , Ils fonctionnent mieux s'ils sont "uniformément répartis". Donc, si une valeur int n'est valide que pour la plage de 1 à 100, vous souhaiterez peut-être "redistribuer" ses codes de hachage pour qu'ils ne fassent pas tous partie du même compartiment.
  • Si vous avez une exigence qui vous fait vous demander comment fonctionne Objects.hash, en termes de fiabilité/performances, réfléchissez bien si le code de hachage est vraiment ce que vous voulez, et implémentez une méthode de codage de hachage personnalisée qui traite vos besoins.
9
Tezra

Joshua Bloch dans son livre Effective Java, 3e édition, p. 53 décourage l'utilisation de Objects.hash(...) si les performances sont critiques.

Les primitives sont en cours d'autobox et la création d'un tableau Object entraîne une pénalité.

2
Ihor M.

Personnellement, je serais d'accord avec le code court en premier, car il est tellement plus rapide à lire, à changer et à vérifier comme correct, ce qui permet d'éviter les bugs lors de la modification de la classe.

Ensuite, pour les classes critiques pour les performances ou lorsque les champs sont coûteux à hacher, je mettrais en cache le résultat (comme le fait String):

// volatile not required for 32-bit sized primitives
private int hash;

@Override
public final int hashCode() {
    // "Racy Single-check idiom" (Item 71, Effective Java 2nd ed.)
    int h = hash;
    if (h == 0) {
        h = Objects.hash(id, timestamp, severity, thread, classPath, message);
        hash = h;
    }
    return h;
}

Dans ce modèle lockless-threadsafe (qui suppose une classe immuable, naturellement), il y a de faibles chances que hash soit initialisé plusieurs fois par différents threads, mais cela n'a pas d'importance car le résultat de la méthode publique est toujours identique . La clé de l'exactitude (de la mémoire-visibilité) est de s'assurer que hash n'est jamais écrit et lu plus d'une fois dans la méthode.

La pénalité de construction de tableau de Objects.hash peut être inférieur à ce que vous imaginez une fois que le code est intégré au JIT et optimisé par le compilateur C2. Heureusement, les bons JDK ont quelque chose qui devrait éliminer essentiellement tous les frais généraux de son utilisation: JEP 348: Java Compiler Intrinsics for JDK APIs

0
Luke Usherwood