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.
Notez que le paramètre de Objects.hash
est Object...
. Cela a deux conséquences principales:
this.id
est converti de long
en Long
.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.
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.
Je voudrais essayer de présenter un argument solide pour les deux.
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.
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é.
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