magicmarcy.de | Equals, HashCode und toString richtig implementieren

Equals, HashCode und toString richtig implementieren

Java • 20. Dezember 2025 • Lesezeit: 6 Minuten

Wer in Java eigene Klassen schreibt, kommt früher oder später an den Punkt, an dem equals(), hashCode() und toString() relevant werden. Diese drei Methoden stammen alle aus der Klasse Object, also der obersten Basisklasse in Java. Deshalb hat jede Klasse sie automatisch - aber oft reicht die Standard-Implementierung nicht aus. Besonders wenn du Objekte miteinander vergleichen oder in Collections wie HashSet oder HashMap speichern möchtest, wird es wichtig, diese Methoden richtig zu überschreiben.

 

Warum equals() überhaupt?

Standardmäßig vergleicht equals() nur, ob zwei Variablen auf dasselbe Objekt im Speicher zeigen. Für einfache Werte wie Strings, Wrapper oder selbstgeschriebene Klassen ist das oft nicht das, was wir wollen. Wenn du z. B. zwei Person-Objekte hast, die zwar verschiedene Speicheradressen haben, aber denselben Namen und dasselbe Geburtsdatum, dann sollen sie inhaltlich als „gleich“ gelten.

public class Person {
    private String name;
    private int alter;

    public Person(String name, int alter) {
        this.name = name;
        this.alter = alter;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;          // gleicher Speicherort
        if (obj == null) return false;         // null-Pruefung
        if (getClass() != obj.getClass()) return false;  // gleiche Klasse?
        Person other = (Person) obj;           // Typ-Cast
        return name.equals(other.name) && alter == other.alter;
    }
}

Damit haben wir erreicht, dass zwei Personen als gleich gelten, wenn Name und Alter übereinstimmen - unabhängig davon, ob sie an derselben Speicheradresse liegen oder nicht.

Person p1 = new Person("Anna", 25);
Person p2 = new Person("Anna", 25);

System.out.println(p1.equals(p2));
// Ausgabe:
true

 

Warum hashCode() dazugehört

Hier lauert eine der größten Fallen für Einsteiger: Immer wenn du equals() überschreibst, musst du auch hashCode() überschreiben. Das hat nichts mit Schönheit oder „Best Practice“ zu tun, sondern mit dem sogenannten Vertrag zwischen equals() und hashCode(). Der besagt:
Wenn zwei Objekte laut equals() gleich sind, müssen sie auch denselben Hashcode haben. Wenn sie unterschiedlich sind, dürfen sie denselben Hashcode haben - müssen aber nicht.
Warum das wichtig ist? Collections wie HashMap oder HashSet nutzen den Hashcode, um Objekte schnell zu finden oder zu prüfen, ob ein Element schon vorhanden ist. Wenn dein Hashcode nicht konsistent mit equals() ist, kann es passieren, dass ein Objekt doppelt in einem Set vorkommt - obwohl equals() sagt, dass es das gleiche ist. Das ist ein Albtraum beim Debuggen.

@Override
public int hashCode() {
    int result = 17;
    result = 31 * result + name.hashCode();
    result = 31 * result + alter;
    return result;
}

Hier kombinieren wir zwei Eigenschaften, die die Person eindeutig machen. Wir starten mit einer Zahl (17), multiplizieren mit einer Primzahl (31) und addieren die Hashcodes der einzelnen Felder. Das ist ein gängiges Muster, um Kollisionen zu vermeiden.
Wenn du Java 7 oder höher nutzt, kannst du es dir mit Objects.hash() noch einfacher machen:

@Override
public int hashCode() {
    return java.util.Objects.hash(name, alter);
}

 

Ein praktisches Beispiel – das Problem bei HashSet

Schauen wir uns mal an, was passiert, wenn equals() und hashCode() nicht zueinander passen.

import java.util.HashSet;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        Set<Person> personen = new HashSet<>();

        Person p1 = new Person("Lisa", 30);
        Person p2 = new Person("Lisa", 30);

        personen.add(p1);
        personen.add(p2);

        System.out.println("Anzahl im Set: " + personen.size());
    }
}

Wenn hashCode() nicht korrekt implementiert ist, ergibt die Ausgabe:

// Ausgabe:
2

Obwohl die beiden Personen laut equals() gleich sind, hält das Set sie für unterschiedlich - weil der Hashcode verschieden ist. Sobald du hashCode() richtig überschreibst, bekommst du:

// Ausgabe:
1

Das zeigt, warum equals() und hashCode() immer zusammengehören - sie sind wie zwei Seiten derselben Medaille.

 

Die toString()-Methode für eine klare Darstellung

toString() ist nicht technisch zwingend nötig, aber extrem hilfreich. Wenn du z. B. in der Konsole ein Objekt ausgibst, bekommst du ohne eigene Implementierung nur so etwas wie:

Person@7ea987ac

Das hilft beim Debuggen nicht wirklich weiter. Also überschreiben wir toString() und geben sinnvolle Informationen aus:

@Override
public String toString() {
    return "Person{name='" + name + "', alter=" + alter + "}";
}
Person p = new Person("Marco", 41);
System.out.println(p);
// Ausgabe:
Person{name='Marco', alter=41}

Schon viel verständlicher, oder? Eine gute toString()-Methode ist ein Segen beim Debuggen, Loggen und Testen.

 

Wichtige Hinweise und typische Fehler

Eine häufige Fehlerquelle ist, in equals() Felder zu vergleichen, die sich ändern können. Wenn du z. B. den Namen einer Person später änderst, verändert sich auch ihr Hashcode - und plötzlich ist das Objekt im HashSet nicht mehr auffindbar. Deshalb sollten Felder, die für Gleichheit und Hashcode herangezogen werden, möglichst unveränderlich sein.
Ein weiteres Problem: Vergiss niemals die Null-Prüfung! Wenn du einfach name.equals(other.name) aufrufst, ohne sicherzustellen, dass name nicht null ist, bekommst du schnell eine NullPointerException. Hier hilft dir die Methode Objects.equals(a, b):

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null || getClass() != obj.getClass()) return false;
    Person other = (Person) obj;
    return alter == other.alter && java.util.Objects.equals(name, other.name);
}

Das ist sicherer und übersichtlicher.

 

Fazit

equals(), hashCode() und toString() sind kleine Methoden mit großer Wirkung. Wer sie versteht und richtig implementiert, verhindert viele schwer zu findende Fehler - besonders bei der Arbeit mit Hash-basierten Collections.
Wenn du eine Faustregel mitnimmst, dann diese:
Immer wenn du equals überschreibst, musst du auch hashCode überschreiben - und toString ist dein bester Freund beim Debuggen.
Damit hast du ein solides Fundament für sauberes, verständliches und zuverlässiges Java - und deine Kollegen werden es dir danken.

Dir hat der Artikel gefallen? Dann spendier mir doch einen Kaffee auf Ko-Fi ☕️ — danke dir! ♥️

Es wurden noch keine Kommentare verfasst, sei der erste!
Über
Avatar

Hi, ich bin Marcel!
Als Fachinformatiker für Anwendungsentwicklung und IHK-geprüfter Ausbilder teile ich auf meinem Blog Grundlagen- und Fortgeschrittenen-Wissen für angehende Entwickler*innen und Interessierte, sowie weitere spannende Themen aus der IT.

Blog Aktivität

Sep
 
Oct
 
 
 
Nov
 
 
 
Dec
 
 
Mon
Wed
Fri