W3docs

Java equals() und hashCode()

equals() und hashCode() in Java korrekt überschreiben, um Collections und wertbasierte Gleichheit zuverlässig zu unterstützen.

equals und hashCode sind die beiden Object-Methoden, auf die Hash-basierte Collections — HashMap, HashSet, LinkedHashMap und alles, was auf Hashing basiert — stillschweigend angewiesen sind. Sind sie korrekt implementiert, verhalten sich Ihre Objekte wie Werte: set.contains(point) findet den Punkt unabhängig davon, welche new Point(3, 4)-Instanz Sie übergeben. Sind sie falsch implementiert, entstehen Duplikate in Sets, fehlende Schlüssel in Maps und Fehler, die sich erst unter Last zeigen.

Der von Object geerbte Standard vergleicht Identität: Zwei Referenzen sind nur dann gleich, wenn sie auf dasselbe Objekt zeigen. Das ist in Ordnung für Dinge wie Datenbankverbindungen, bei denen jede Instanz eine eigene Ressource ist. Für wertähnliche Klassen — Geldbeträge, Punkte, Namen, Daten — möchte man fast immer Inhaltsgleichheit, und das bedeutet, beide Methoden gemeinsam zu überschreiben.

Der Vertrag

equals muss vier Regeln erfüllen:

  • Reflexivx.equals(x) ist true.
  • Symmetrischx.equals(y) genau dann, wenn y.equals(x).
  • Transitiv — wenn x.equals(y) und y.equals(z), dann auch x.equals(z).
  • Konsistent — wiederholte Aufrufe mit unveränderten Feldern liefern dasselbe Ergebnis.

Außerdem: x.equals(null) muss false zurückgeben und darf niemals eine Exception werfen.

hashCode hat eine Regel, die sie mit equals verknüpft:

  • Gleiche Objekte müssen gleiche Hash-Codes haben. Ungleiche Objekte dürfen denselben Hash-Code haben (Kollisionen sind erlaubt, aber schlecht für die Performance).

Diese eine Regel ist der Grund, warum man nie eine Methode ohne die andere überschreiben kann. Wenn a.equals(b) gilt, aber a.hashCode() != b.hashCode(), legt HashSet sie in verschiedene Buckets, contains findet den falschen, und man hat ein Ghost-Duplikat.

Den Vertragsbruch beobachten

Diese Klasse überschreibt equals, vergisst aber hashCode, sodass sie den identitätsbasierten Hash von Object erbt. Die beiden Objekte sind "gleich", landen aber in verschiedenen Buckets — contains kann das soeben hinzugefügte Objekt nicht finden:

java— editable, runs on the server

equals sagt, dass die Objekte gleich sind, aber das Set kann das zweite nicht finden. Überschreibt man hashCode entsprechend, gelingt die Suche.

Aufbau eines korrekten equals

Ein funktionierendes equals folgt einem standardmäßigen Muster:

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);
  }
}

Schritt für Schritt:

  • Identitäts-Kurzschluss. this == o erfasst den häufigen Fall schnell.
  • Typprüfung mit Bindung. instanceof Point p weist nulls und falsche Typen in einem Ausdruck zurück und bindet die eingeschränkte Referenz.
  • Feldvergleich. Verwenden Sie == für primitive Typen, Objects.equals(a, b) für nullable Referenzen, Float.compare / Double.compare für Gleitkommazahlen.

Objects.hash(...) berechnet einen Hash aus einer Liste von Feldern. Es ist etwas langsamer als handgeschriebener XOR/Multiply-Code, aber korrekt und eindeutig.

getClass oder instanceof?

Zwei Lager:

  • instanceof erlaubt es, dass eine Subklassen-Instanz einer Elterninstanz gleich ist, wenn die verglichenen Felder identisch sind. Etwas flexibler.
  • getClass() besteht auf der exakten Laufzeitklasse. Einfacher symmetrisch über Hierarchien hinweg zu halten, bricht aber die Substituierbarkeit.

Für die meisten wertähnlichen Klassen ist der einfachste Weg, die Klasse final zu machen und instanceof zu verwenden. Ohne final ist das Mischen beider Stile in einer Hierarchie der häufigste Ursprung von Gleichheitsfehlern. Records umgehen die Entscheidung vollständig — sie sind implizit final, und das generierte equals verwendet eine Prüfung auf den exakten Typ.

Gleitkommafelder

Verwenden Sie == nicht für double- oder float-Felder — der Wert +0.0 ist unter == gleich -0.0, aber Double.compare behandelt sie unterschiedlich, und NaN == NaN ist false. Double.compare(a, b) == 0 und Float.compare liefern die konsistente Antwort, die der Vertrag erfordert.

Arrays

Object.equals auf einem Array vergleicht Referenzen, nicht den Inhalt. Verwenden Sie Arrays.equals(a, b) für eindimensionale Arrays, Arrays.deepEquals für mehrdimensionale. Ebenso verwenden Sie Arrays.hashCode / Arrays.deepHashCode in hashCode.

Veränderlichkeit ist feindlich gegenüber Hash-basierten Collections

Wenn Sie ein Feld, das Teil von equals/hashCode ist, nach dem Einfügen des Objekts in ein HashSet verändern, passt der Bucket, in dem das Set es abgelegt hat, nicht mehr zum neuen Hash — und das Objekt wird durch contains unerreichbar. Die sicherste Regel: Felder, die in equals verwendet werden, sollten final sein. Wenn das nicht möglich ist, fügen Sie das Objekt niemals in eine Hash-basierte Collection ein.

Für einfache Datenklassen keines von beiden von Hand schreiben

Wenn die Klasse ein reiner Datenträger ist, bevorzugen Sie einen Record — der Compiler generiert korrektes equals und hashCode für Sie, und die beiden bleiben immer synchron, wenn sich Felder ändern. Wenn Sie keinen Record verwenden können, ist der "generate equals/hashCode"-Befehl Ihrer IDE die nächstbeste Option.

Ein ausgearbeitetes Beispiel

java— editable, runs on the server

Was kommt als Nächstes

equals ermöglicht es Ihren Objekten, sich selbst zu vergleichen; toString ermöglicht es ihnen, sich selbst zu beschreiben. Das nächste Kapitel handelt davon, toString zu überschreiben, um eine Ausgabe zu erzeugen, die in Logs, Fehlermeldungen und Debuggern wirklich nützlich ist. Weiter zu Java toString-Methode.

Übungen

Übung
Warum ist es ein Fehler, `equals` zu überschreiben, ohne auch `hashCode` zu überschreiben?
Warum ist es ein Fehler, `equals` zu überschreiben, ohne auch `hashCode` zu überschreiben?
Was this page helpful?