Der equals/hashCode-Vertrag
Warum beide übereinstimmen müssen
Der equals/hashCode-Vertrag ist eine kostenlose Java Academy-Lektion auf CoddyKit. Dies ist Lektion 2 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Java Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Java Academy-Kurs umfasst insgesamt 4 Lektionen.
Zwei Methoden, ein Vertrag
Jedes Java-Objekt erbt equals() und hashCode() von Object.
Wenn Sie eine Methode überschreiben, müssen Sie fast immer auch die andere überschreiben. Beide bilden einen verbindlichen Vertrag, auf den sich hashbasierte Collections verlassen.
public class Main {
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
System.out.println(a.equals(b));
System.out.println(a.hashCode() == b.hashCode());
}
}Die Grundregel
Der Vertrag besagt:
- Wenn
a.equals(b)true ergibt, mussa.hashCode()gleichb.hashCode()sein. - Unterscheiden sich die Hashcodes, sind die Objekte garantiert nicht gleich.
Gleiche Objekte müssen gleiche Hashcodes haben. Umgekehrt ist das nicht erforderlich.
public class Main {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hel" + "lo";
System.out.println(s1.equals(s2));
System.out.println(s1.hashCode() == s2.hashCode());
}
}Ungleiche Objekte dürfen denselben Hash haben
Zwei ungleiche Objekte dürfen denselben Hashcode haben. Das ist lediglich eine Kollision, und die Map behandelt sie mit equals().
Die einzige unzulässige Situation ist daher: gleiche Objekte mit unterschiedlichen Hashcodes.
public class Main {
public static void main(String[] args) {
// Equal value, unequal objects, same hash is fine
System.out.println("FB".hashCode() == "Ea".hashCode());
System.out.println("FB".equals("Ea"));
}
}Eine Klasse, die nur equals überschreibt
Hier sehen Sie eine fehlerhafte Klasse. Sie überschreibt equals(), aber nicht hashCode().
Zwei logisch gleiche Punkte haben nun unterschiedliche Hashcodes, die sie von Object erben, und verletzen damit den Vertrag.
public class Main {
static class Point {
final int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override public boolean equals(Object o) {
if (!(o instanceof Point p)) return false;
return x == p.x && y == p.y;
}
// BUG: no hashCode override
}
public static void main(String[] args) {
Point a = new Point(1, 2);
Point b = new Point(1, 2);
System.out.println("equals: " + a.equals(b));
System.out.println("same hash: " + (a.hashCode() == b.hashCode()));
}
}Der Fehler in einem HashSet
Die fehlerhafte Klasse funktioniert in einem HashSet nicht korrekt. Das Set sucht in dem Bucket, der durch den falschen Hash ausgewählt wurde, und findet das gleiche Element daher nie.
So entstehen Duplikate, die eigentlich abgewiesen werden sollten.
import java.util.HashSet;
import java.util.Set;
public class Main {
static class Point {
final int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override public boolean equals(Object o) {
if (!(o instanceof Point p)) return false;
return x == p.x && y == p.y;
}
}
public static void main(String[] args) {
Set<Point> set = new HashSet<>();
set.add(new Point(1, 2));
set.add(new Point(1, 2));
System.out.println("size = " + set.size());
}
}Die Korrektur
Überschreiben Sie beide Methoden und leiten Sie sie aus denselben Feldern ab. Dann haben gleiche Objekte denselben Hash und landen im selben Bucket.
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class Main {
static class Point {
final int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override public boolean equals(Object o) {
if (!(o instanceof Point p)) return false;
return x == p.x && y == p.y;
}
@Override public int hashCode() { return Objects.hash(x, y); }
}
public static void main(String[] args) {
Set<Point> set = new HashSet<>();
set.add(new Point(1, 2));
set.add(new Point(1, 2));
System.out.println("size = " + set.size());
}
}equals muss reflexiv sein
Ein korrektes equals() erfüllt vier Eigenschaften. Die erste ist Reflexivität: x.equals(x) muss true sein.
Das klingt offensichtlich, aber eigene Logik kann diese Eigenschaft verletzen, wenn Sie den Fall derselben Referenz nicht behandeln.
public class Main {
public static void main(String[] args) {
String x = "java";
System.out.println(x.equals(x));
}
}Symmetrisch und transitiv
Zwei weitere Regeln:
- Symmetrisch: Wenn
a.equals(b), dann auchb.equals(a). - Transitiv: Wenn
a.equals(b)undb.equals(c), dann aucha.equals(c).
Das Mischen von Typen innerhalb einer Klassenhierarchie verletzt häufig die Symmetrie. Vergleichen Sie Klassen daher sorgfältig.
public class Main {
public static void main(String[] args) {
String a = "x", b = "x", c = "x";
boolean symmetric = a.equals(b) == b.equals(a);
boolean transitive = a.equals(b) && b.equals(c) && a.equals(c);
System.out.println("symmetric: " + symmetric);
System.out.println("transitive: " + transitive);
}
}Konsistent
Die vierte Regel lautet konsistent: Wiederholte Aufrufe liefern dasselbe Ergebnis, solange sich die Objekte nicht ändern.
Deshalb sollten Sie equals und hashCode auf unveränderlichen Feldern basieren. Wenn Sie ein für hashCode verwendetes Feld nach dem Einfügen ändern, beschädigen Sie die Map.
import java.util.Objects;
public class Main {
public static void main(String[] args) {
// Immutable record gives consistent equals/hashCode automatically
record Id(int value) {}
Id id = new Id(7);
System.out.println(Objects.equals(id, new Id(7)));
System.out.println(id.hashCode() == new Id(7).hashCode());
}
}Records erledigen das für Sie
Ein Java-record erzeugt automatisch equals() und hashCode() aus all seinen Komponenten.
Für Daten mit Wertsemantik sollten Sie Records bevorzugen. Der Vertrag wird automatisch erfüllt und bleibt synchron.
import java.util.HashSet;
import java.util.Set;
public class Main {
record Point(int x, int y) {}
public static void main(String[] args) {
Set<Point> set = new HashSet<>();
set.add(new Point(1, 2));
set.add(new Point(1, 2));
System.out.println("size = " + set.size());
}
}Die Falle veränderlicher Schlüssel
Wenn Sie ein für hashCode verwendetes Feld ändern, nachdem Sie das Objekt in eine Map eingefügt haben, können Sie es nicht mehr finden.
Die Map sucht im alten Bucket, aber das Objekt wird nun einem neuen Bucket zugeordnet. Der Eintrag wird zu einem verlorenen Geistereintrag.
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class Main {
static class Box {
int id;
Box(int id) { this.id = id; }
@Override public boolean equals(Object o) {
return o instanceof Box b && b.id == id;
}
@Override public int hashCode() { return Objects.hash(id); }
}
public static void main(String[] args) {
Map<Box, String> m = new HashMap<>();
Box key = new Box(1);
m.put(key, "value");
key.id = 99; // mutated after insertion
System.out.println(m.get(key));
}
}Kurze Überprüfung
Testen Sie Ihr Verständnis des Vertrags.
Zusammenfassung
Sie haben den equals/hashCode-Vertrag kennengelernt:
- Gleiche Objekte müssen gleiche Hashcodes haben.
- equals muss reflexiv, symmetrisch, transitiv und konsistent sein.
- Basieren Sie beide Methoden auf denselben unveränderlichen Feldern.
- Records erzeugen korrekte Implementierungen automatisch.
Als Nächstes schreiben Sie Ihren eigenen korrekten hashCode von Hand.
import java.util.Objects;
public class Main {
public static void main(String[] args) {
System.out.println("Contract recap: " + Objects.hash(1, 2, 3));
}
}Häufig gestellte Fragen
Ist die Lektion „Der equals/hashCode-Vertrag“ kostenlos?
Ja — der vollständige Text von „Der equals/hashCode-Vertrag“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Java Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Java Academy-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „Der equals/hashCode-Vertrag“?
Warum beide übereinstimmen müssen Du übst Java Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um Java Academy zu starten?
Keine Vorkenntnisse erforderlich. Java Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 2 von 4.
Wie lange dauert die Lektion „Der equals/hashCode-Vertrag“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser Java Academy-Lektion Code schreiben und ausführen?
Ja. Jede Java Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- Funktionsweise von HashMap
- Der equals/hashCode-Vertrag
- hashCode implementieren
- Treeification und Performance