ProgrammationDéveloppeur Java

Comment sont conçues et utilisées les méthodes equals() et hashCode() en Java ? Quelles sont les conséquences de leurs implémentations incorrectes ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

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 :

  • Si a.equals(b) == true, alors a.hashCode() == b.hashCode()
  • Si 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 :

  • La méthode equals() doit être réflexive, symétrique, transitive et cohérente.
  • La méthode hashCode() doit renvoyer la même valeur pour un objet avec des données immuables.
  • Dans les méthodes redéfinies, seules les propriétés significatives doivent être comparées.

Questions piégeuses.

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.

Erreurs typiques et anti-modèles

  • Ne pas redéfinir hashCode() lors de la redéfinition de equals().
  • Utiliser des champs mutables dans le calcul de hashCode().
  • Ignorer le contrat entre ces méthodes.

Exemple de la vie réelle

Cas négatif

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 :

  • Code minimal

Inconvénients :

  • Fonctionnement de la collection perturbé
  • Bugs vagues et difficiles à détecter dans l’application

Cas positif

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 :

  • Comportement prévisible
  • Bon fonctionnement des collections de hachage

Inconvénients :

  • Nécessité de maintenir la logique à jour lors des modifications de la classe