In Java sind die Methoden equals() und hashCode() von entscheidender Bedeutung für das korrekte Funktionieren von Sammlungen wie HashMap, HashSet und anderen. Diese Frage wird oft von Anfängern übersehen, obwohl Verstöße gegen die Verträge dieser Methoden zu schwer erkennbaren Fehlern in der Logik von Anwendungen führen können.
Hintergrund der Frage:
In der Programmiersprache Java erben alle Klassen ursprünglich die Methoden equals() und hashCode() von der Klasse Object. Standardmäßig vergleicht equals() die Referenzen auf Objekte (d.h. deren physikalische Position im Speicher), während hashCode() einen eindeutigen Code für jedes Objekt zurückgibt. Für Benutzerklassen ist es jedoch oft notwendig, Objekte nach ihrem Inhalt und nicht nach ihrer Referenz zu vergleichen.
Problem:
Wenn die Methoden equals() und hashCode() nicht überschrieben oder fehlerhaft überschrieben sind, können Objekte in Sammlungen, die auf Hashierung basieren, unerwartetes Verhalten zeigen. Dies führt zu fehlenden Elementen, Duplikaten oder Suchfehlern.
Lösung:
Beide Methoden sollten immer gemeinsam überschrieben werden, wobei der Vertrag strikt eingehalten wird:
a.equals(b) == true, dann a.hashCode() == b.hashCode()a.equals(b) == false, ist die Anforderung an hashCode so, dass Einzigartigkeit nicht erforderlich ist.Beispiel für eine korrekte Implementierung:
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); } }
Wichtige Merkmale:
equals() muss reflexiv, symmetrisch, transitiv und konsistent sein.hashCode() muss denselben Wert für ein Objekt bei unveränderten Daten zurückgeben.Kann man equals() ohne hashCode() in Klassen verwenden, die in HashSet gespeichert werden?
Nein. Wenn Sie nur equals() überschreiben, können Sammlungen, die auf Hashierung basieren, die Eindeutigkeit von Objekten nicht korrekt bestimmen. HashSet vergleicht zunächst hashCode und dann equals.
Muss man alle Felder der Klasse in equals() und hashCode() verwenden?
Nein. Nur die, die für die logische Identität der Klasse relevant sind. Wenn ein Objekt beispielsweise einen internen, eindeutigen Identifikator hat, reicht dieser aus.
@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); }
Kann man sich in equals() auf einen Getter anstelle eines direkten Feldes stützen?
In der Regel ja, solange es keine Nebeneffekte gibt und der Getter stabil ist. Es besteht jedoch die Gefahr, dass der Getter bei verschiedenen Aufrufen unterschiedliche Werte zurückgibt – dann wird das Verhalten unvorhersehbar.
hashCode() nicht überschreiben, wenn equals() überschrieben wird.hashCode() verwenden.Ein Entwickler implementiert die Klasse User und definiert nur die Methode equals(), vergisst hashCode(). Infolgedessen entstehen Duplikate und "verlorene" Elemente beim Hinzufügen und Suchen von Objekten in HashSet.
Vorteile:
Nachteile:
Ein Entwickler implementiert beide Methoden strikt nach Vertrag und verwendet nur die ID innerhalb der Logik der Gleichheit und Hashierung. Sammlungen verhalten sich erwartungsgemäß, Suche und Speicherung funktionieren korrekt.
Vorteile:
Nachteile: