En Java, les méthodes equals() et hashCode() sont extrêmement importantes pour le bon fonctionnement des collections, telles que HashMap, HashSet, et d'autres. Cette question est souvent négligée par les développeurs débutants, bien que le non-respect des contrats de ces méthodes entraîne des erreurs logiques difficiles à détecter dans les applications.
Historique de la question :
Dans le langage Java, toutes les classes héritent à l'origine des méthodes equals() et hashCode() de la classe Object. Par défaut, equals() compare les références aux objets (c'est-à-dire leur emplacement physique en mémoire), tandis que hashCode() renvoie un code unique pour chaque objet. Cependant, pour les classes personnalisées, il est souvent nécessaire de comparer les objets par leur contenu et non par leur référence.
Problème :
Si les méthodes equals() et hashCode() ne sont pas redéfinies ou sont redéfinies de manière incorrecte, les objets peuvent se comporter de manière inattendue dans des collections basées sur le hachage. Cela peut entraîner l'absence d'éléments, des doublons ou des erreurs de recherche.
Solution :
Il est essentiel de redéfinir les deux méthodes ensemble, en respectant strictement le contrat suivant :
a.equals(b) == true, alors a.hashCode() == b.hashCode()a.equals(b) == false, l'exigence pour hashCode n'est pas celle de l'unicitéExemple d'implémentation correcte :
public class Person { private final String name; private final int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name, age); } }
Caractéristiques clés :
equals() doit être réflexive, symétrique, transitive et cohérente.hashCode() doit renvoyer la même valeur pour un objet avec des données immuables.Peut-on utiliser seulement equals() sans hashCode() dans des classes qui seront stockées dans HashSet ?
Non. Si vous avez redéfini seulement equals(), les collections basées sur le hachage ne détermineront pas correctement l'unicité des objets. HashSet compare d'abord hashCode, puis equals.
Est-il nécessaire d'utiliser tous les champs de la classe dans equals() et hashCode()?
Non. Seules les propriétés significatives pour l'identité logique de la classe. Par exemple, si un objet a un identifiant interne unique, cela suffit.
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return Objects.equals(id, user.id); }
Peut-on se baser sur le getter au lieu du champ direct dans equals()?
En général, oui, s'il n'y a pas d'effets secondaires et que le getter est stable. Mais il y a un risque que le getter renvoie des valeurs différentes lors de différents appels — dans ce cas, le comportement sera imprévisible.
hashCode() lors de la redéfinition de equals().hashCode().Un développeur implémente la classe User et définit uniquement la méthode equals(), oubliant hashCode(). Le résultat de l’ajout et de la recherche d'objets dans un HashSet entraîne des doublons et des éléments "perdus".
Avantages :
Inconvénients :
Un développeur implémente les deux méthodes strictement selon le contrat, n'utilisant que l'id dans la logique d'égalité et de hachage. Les collections se comportent comme prévu, la recherche et le stockage fonctionnent correctement.
Avantages :
Inconvénients :