Java equal () et hashCode () basés sur des champs différents?

Existe-t-il une situation dans laquelle une classe aurait intérêt à implémenter ses méthodes equals() et hashCode() utilisant un ensemble différent de champs de classe?

Je pose la question parce que je suis insortinggué par les générateurs Netbeans equals() et hashCode() , où il vous est demandé de choisir les champs à inclure dans chaque méthode séparément. Je finis toujours par sélectionner les mêmes champs pour les deux méthodes, mais y a-t-il une situation où ce n’est pas le bon choix?

Bien, equals() doit utiliser tous les champs utilisés par hashCode() , sinon vous pourriez obtenir des codes de hachage différents pour des objects égaux. L’inverse n’est cependant pas vrai – vous pouvez choisir de ne pas tenir compte d’un champ particulier lors du choix du code de hachage. De cette façon, vous pourriez vous retrouver avec le même code de hachage pour deux objects inégaux qui ne diffèrent que par ce champ “non utilisé” (par opposition aux collisions naturelles). Vous ne voudriez que dans une situation où vous saviez que les collisions seraient improbables mais où vous alliez vous battre beaucoup . J’imagine que c’est extrêmement rare 🙂

Dans un autre cas, il existe une sorte de comparaison d’égalité personnalisée (par exemple, des comparaisons de chaînes ne respectant pas la casse) où il est difficile ou coûteux de générer un code de hachage pour le champ. Encore une fois, cela augmenterait les risques de collision mais serait valable.

Jon Skeet a bien répondu à cette question (comme il le fait toujours). Cependant, j’aimerais append qu’il s’agit d’une implémentation valable pour toute implémentation d’égal à égal

 public int hashCode() { return 42; } 

Naturellement, les performances des structures de données hachées vont se dégrader considérablement. Néanmoins, il est préférable de tuer les performances que de les briser. Donc, si jamais vous décidez de remplacer des équivalents mais que vous ne voyez pas la nécessité de fournir une implémentation saine de hashCode, c’est la voie à suivre du paresseux.

En règle générale, vous devez utiliser les mêmes champs. De la documentation equals() :

Notez qu’il est généralement nécessaire de remplacer la méthode hashCode chaque fois que cette méthode est remplacée, afin de conserver le contrat général pour la méthode hashCode, qui stipule que les objects égaux doivent avoir des codes de hachage égaux.

De la documentation hashCode() :

Si deux objects sont égaux selon la méthode equals (Object), alors l’appel de la méthode hashCode sur chacun des deux objects doit produire le même résultat entier.

Notez que l’inverse n’est pas vrai – vous pouvez avoir deux objects avec le même hashcode qui ne sont pas égaux (c’est ainsi que certaines structures de données résolvent les collisions)

Donc, en théorie, il est possible d’utiliser un sous-ensemble des champs de méthode equals(..) pour la hashCode() , mais je ne peux pas penser que ce soit une raison pratique.

Je ne pense pas qu’il y en a. J’ai déjà blogué à propos de ce sujet – je pense que c’est une faille de l’interface utilisateur dans NetBeans qui vous permet de les choisir indépendamment les uns des autres. De mon blog:

Ce post de bytes.com explique bien ce qui suit :

Remplacement de la méthode hashCode.

Le contrat pour la méthode equals devrait vraiment comporter une autre ligne disant que vous devez remplacer la méthode hashCode après avoir surchargé la méthode equals. La méthode hashCode est prise en charge au profit des collections basées sur le hachage.

Le contrat

Encore une fois à partir des spécifications:

Chaque fois qu’il est invoqué sur le même object plusieurs fois pendant l’exécution d’une application, la méthode hashCode doit systématiquement renvoyer le même entier, à condition qu’aucune information utilisée dans les comparaisons d’égal à égal sur l’object ne soit modifiée. Cet entier ne doit pas nécessairement restr cohérent d’une exécution d’une application à une autre exécution de la même application. Si deux objects sont égaux selon la méthode equals (Object), alors l’appel de la méthode hashCode sur chacun des deux objects doit produire le même résultat entier. Il n’est pas nécessaire que si deux objects ne soient pas égaux selon la méthode equals, l’appel de la méthode hashCode sur chacun des deux objects doit produire des résultats entiers distincts. Toutefois, le programmeur doit savoir que la production de résultats entiers distincts pour des objects inégaux peut améliorer les performances des tables de hachage. Les objects égaux doivent donc avoir des hashCodes égaux. Un moyen simple de s’assurer que cette condition est toujours remplie consiste à utiliser les mêmes atsortingbuts que ceux utilisés pour déterminer l’égalité dans la détermination du hashCode. Vous devriez maintenant voir pourquoi il est important de remplacer hashCode chaque fois que vous écrasez égal.

Cette phrase du dernier paragraphe le résume: « Un moyen simple de s’assurer que cette condition est toujours remplie consiste à utiliser les mêmes atsortingbuts que ceux utilisés pour déterminer l’égalité dans la détermination du hashCode» .

Suite à la réponse de Jon Skeet, j’ai récemment rencontré un cas où je devais implémenter la méthode hashCode avec seulement un sous-ensemble des champs utilisés dans la méthode des égaux. Le scénario (simplifié) est le suivant:

J’ai deux classes A et B qui contiennent chacune une référence à l’autre en plus d’avoir une clé Ssortingng définie. En utilisant le hashCode automatique et le générateur égal à Eclipse (qui, contrairement à Netbeans, ne permet d’utiliser que les mêmes champs dans les deux méthodes), je me retrouve avec les classes suivantes:

 public class A { public B b; public Ssortingng bKey; @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((b == null) ? 0 : b.hashCode()); result = prime * result + ((bKey == null) ? 0 : bKey.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof A)) return false; A other = (A) obj; if (b == null) { if (other.b != null) return false; } else if (!b.equals(other.b)) return false; if (bKey == null) { if (other.bKey != null) return false; } else if (!bKey.equals(other.bKey)) return false; return true; } } public class B { public A a; public Ssortingng aKey; @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((a == null) ? 0 : a.hashCode()); result = prime * result + ((aKey == null) ? 0 : aKey.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof B)) return false; B other = (B) obj; if (a == null) { if (other.a != null) return false; } else if (!a.equals(other.a)) return false; if (aKey == null) { if (other.aKey != null) return false; } else if (!aKey.equals(other.aKey)) return false; return true; } } 

Le problème est survenu lorsque j’ai essayé d’append la classe A à un hachage de la manière suivante:

  public static void main(Ssortingng[] args) { A a = new A(); B b = new B(); ab = b; ba = a; Set aSet = new HashSet(); aSet.add(a); } 

Cela se terminera par une erreur StackOverflowEr, car l’ajout d’ a aSet entraînera l’appel de la méthode hashCode, ce qui entraînera l’appel du hashCode b , ce qui entraînera l’appel du hashCode , etc., etc., etc. La seule façon de contourner ce Ssortingng bKey consiste à supprimer la référence à A du hashCode de B et à equals OU d’inclure uniquement la Ssortingng bKey dans la méthode hashCode de B Puisque je voulais que la méthode B.equals inclue la référence A dans la vérification de l’égalité, la seule chose que je pouvais faire était de faire en B.hashCode que B.hashCode n’utilise qu’un sous-ensemble des champs utilisés dans B.equals c’est-à-dire uniquement dans B.hashCode . Je ne pouvais pas voir autrement.

Peut-être que ma conception est défectueuse et je serais heureux de faire remarquer cela, mais c’est essentiellement ainsi que mes objects de domaine sont structurés dans mon programme actuel.

Lisez Java en vigueur au chapitre 3: ” Toujours remplacer le hashCode lorsque vous remplacez égal à “.

Et je pense que si votre object ne sera jamais placé dans une collection basée sur le hachage, vous n’avez pas besoin de remplacer le hashCode.