W3docs

Java equals() et hashCode()

Redéfinissez correctement equals() et hashCode() dans vos classes Java pour prendre en charge les collections et l'égalité basée sur les valeurs.

equals et hashCode sont les deux méthodes de Object sur lesquelles les collections basées sur le hachage — HashMap, HashSet, LinkedHashMap, tout ce qui repose sur le hachage — s'appuient silencieusement. Implémentez-les correctement et vos objets se comportent comme des valeurs : set.contains(point) trouve le point quelle que soit l'instance new Point(3, 4) que vous passez. Implémentez-les incorrectement et vous obtenez des doublons dans les ensembles, des clés manquantes dans les maps, et des bugs qui n'apparaissent que sous charge.

La version héritée de Object compare l'identité : deux références sont égales uniquement lorsqu'elles pointent vers le même objet. C'est adapté pour des choses comme les connexions de base de données, où chaque instance est une ressource distincte. Pour les classes de type valeur — argent, points, noms, dates — vous voudrez presque toujours l'égalité de contenu à la place, ce qui implique de redéfinir les deux méthodes ensemble.

Le contrat

equals doit satisfaire quatre règles :

  • Réflexivex.equals(x) est vrai.
  • Symétriquex.equals(y) si et seulement si y.equals(x).
  • Transitive — si x.equals(y) et y.equals(z), alors x.equals(z).
  • Cohérente — des appels répétés avec des champs inchangés donnent la même réponse.

De plus : x.equals(null) doit retourner false, jamais lever d'exception.

hashCode a une règle qui le lie à equals :

  • Les objets égaux doivent avoir des codes de hachage égaux. Les objets inégaux peuvent partager un code de hachage (les collisions sont autorisées, elles nuisent simplement aux performances).

Cette règle unique explique pourquoi vous ne pouvez pas redéfinir l'une sans l'autre. Si a.equals(b) mais a.hashCode() != b.hashCode(), HashSet les place dans des compartiments différents, contains trouve le mauvais, et vous avez un doublon fantôme.

Observation de la rupture du contrat

Cette classe redéfinit equals mais oublie hashCode, donc elle hérite du hachage basé sur l'identité de Object. Les deux objets sont « égaux » mais se retrouvent dans des compartiments différents — contains ne peut pas trouver celui que vous venez d'ajouter :

java— editable, runs on the server

equals indique que les objets sont identiques, mais l'ensemble ne peut pas trouver le second. Redéfinissez hashCode de manière cohérente et la recherche réussit.

Anatomie d'un equals correct

Un equals fonctionnel suit une structure standard :

public final class Point {
  private final int x;
  private final int y;
  public Point(int x, int y) { this.x = x; this.y = y; }

  @Override
  public boolean equals(Object o) {
    if (this == o)                      return true;
    if (!(o instanceof Point p))        return false;
    return x == p.x && y == p.y;
  }

  @Override
  public int hashCode() {
    return Objects.hash(x, y);
  }
}

Étape par étape :

  • Court-circuit d'identité. this == o gère rapidement le cas courant.
  • Vérification de type avec liaison. instanceof Point p rejette les valeurs nulles et les mauvais types en une seule expression et lie la référence réduite.
  • Comparaison de champs. Utilisez == pour les primitives, Objects.equals(a, b) pour les références nullables, Float.compare / Double.compare pour les flottants.

Objects.hash(...) construit un hachage à partir d'une liste de champs. C'est légèrement plus lent que du code XOR/multiplication écrit à la main, mais c'est correct et sans ambiguïté.

getClass ou instanceof ?

Deux approches s'affrontent :

  • instanceof permet à une instance de sous-classe d'être égale à une instance parent si l'ensemble de champs comparés est identique. Légèrement plus flexible.
  • getClass() exige la même classe d'exécution exacte. Plus facile à maintenir symétrique dans les hiérarchies, mais rompt la substituabilité.

Pour la plupart des classes de type valeur, la voie la plus simple est de rendre la classe final et d'utiliser instanceof. Sans final, mélanger les deux styles dans une hiérarchie est là où vivent la plupart des bugs d'égalité. Les records contournent entièrement la décision — ils sont implicitement finaux et le equals généré utilise une vérification de type exacte.

Champs à virgule flottante

N'utilisez pas == sur des champs double ou float — la valeur +0.0 est égale à -0.0 avec ==, mais Double.compare les traite différemment, et NaN == NaN est false. Double.compare(a, b) == 0 et Float.compare donnent la réponse cohérente requise par le contrat.

Tableaux

Object.equals sur un tableau compare les références, pas le contenu. Utilisez Arrays.equals(a, b) pour les tableaux unidimensionnels, Arrays.deepEquals pour les multidimensionnels. De même, utilisez Arrays.hashCode / Arrays.deepHashCode dans hashCode.

La mutabilité est hostile aux collections basées sur le hachage

Si vous modifiez un champ qui fait partie de equals/hashCode après avoir placé l'objet dans un HashSet, le compartiment où l'ensemble l'avait placé ne correspond plus au nouveau hachage — et l'objet devient inaccessible via contains. La règle la plus sûre : les champs utilisés dans equals doivent être final. Si ce n'est pas possible, ne placez jamais l'objet dans une collection basée sur le hachage.

N'écrivez aucune des deux méthodes à la main pour les classes de données simples

Si la classe est un simple conteneur de données, préférez un record — le compilateur génère pour vous un equals et un hashCode corrects, et les deux resteront toujours synchronisés à mesure que les champs changent. Si vous ne pouvez pas utiliser un record, la commande « generate equals/hashCode » de votre IDE est la meilleure alternative.

Un exemple complet

java— editable, runs on the server

La suite

equals permet à vos objets de se comparer ; toString leur permet de se décrire. Le prochain chapitre porte sur la redéfinition de toString pour produire une sortie réellement utile dans les logs, les messages d'erreur et les débogueurs. Continuez vers la méthode toString de Java.

Pratique

Pratique
Pourquoi est-ce un bug de redéfinir `equals` sans redéfinir aussi `hashCode` ?
Pourquoi est-ce un bug de redéfinir `equals` sans redéfinir aussi `hashCode` ?
Was this page helpful?