equals() und hashcode() überschreiben

Das leidige Thema equals() und hashcode()… Man muß glaube ich nicht erklären, wieso man die zwei Methoden überschreiben soll. Vielmehr ist es interessanter, wie man diese richtig überschreibt. Folgende Punkte müssen dabei berücksichtigt werden:

  • Welche Felder sind wichtig für den Vergleich auf Gleichheit?
  • Wie wichtig ist die Ausführungsgeschwindigkeit?
  • Wie sorge ich dafür, daß ich nicht vergesse equals() und hashcode() zu erweitern, wenn die Klasse geändert wurde (neue Felder kamen hinzu)?

Im Hinblick auf diese Punkte, betrachten wir diese drei Möglichkeiten equals() und hashcode() zu implementieren:

  1. Reguläre Implementierung ohne Hilfsbibliotheken.
  2. EqualsBuilder & HashcodeBuilder aus commons-lang3 per Builderklasse
  3. EqualsBuilder & HashcodeBuilder aus commons-lang3 per Reflection

Die Implementierungen werden an folgender sehr einfachen (Kontainer- oder DTO-) Klasse getestet:

Reguläre Implementierung

Normalerweise werden die equals() und hashcode()– Methoden nicht von Hand implementiert, sondern von einer IDE generiert, sodaß es zu solchen Ergebnis kommt:

Der größte Vorteil dieser Lösung ist die Einfachheit – man muß über die Implementierung nicht viel nachdenken. Außerdem bieten die meisten IDEs eine entsprechende Generator-Funktion ohne zusätzlichen Plugins an.

Diese Lösung hat auch einige Nachteile:

  • Die Methoden müssen für jede Klasse extra generiert werden und das jedes Mal, wenn die Klasse sich ändert
  • Selbst in dieser einfachen Klasse benötigt die Implementierung ziemlich viel Codezeilen. Das macht die Klasse unleserlich, unübersichtlich.
  • Verschiedene IDEs können die equals() und hashcode()-Methoden auf unterschiedliche Art und Weise implementieren (instanceof statt getClass()). Das kann man aber standardisieren.

Builder-Pattern (Erbauer) mit Apache Lang

Weitere Möglichkeit die equals() und hashcode() zu implementieren, bietet sich mit Hilfe von HashCodeBuilder und EqualsBuilder aus der Bibliothek apache-lang3:

Der größte Vorteil dieser Lösung ist sind Klarheit und Übersichtlichkeit. Keine unnötigen if-Anfragen und vor allem keine Null-Überprüfungen. Mit ein wenig Aufwand ist es auch hier möglich, die equals() und hashcode() mit IDE zu generieren.

Die Nachteile sind aber schwerwiegender:

  • Man muss nachwievor die Methoden für jede Klasse extra generieren
  • Die Implementierung hängt nun von einer externen Bibliothek ab
  • Der Referenz- sowie Klassenvergleich müssen trotzdem implementiert werden
  • Schlechtere Performance

Vor allem ist die Ausführungsgeschwindigkeit der equals()-Methode, wenn auch minimal, schlechter als die reguläre Implementierung. Der EqualsBilder prüft bei jedem append auf die Gleichheit. Falls beispielsweise die Id’s verschieden sind, werden die weiteren drei Anweisungen trotzdem ausgeführt, wobei der Builder keine Vergleiche mehr ausführt:

Reflection-Ansatz mit Apache Lang

Die dritte Implementierungsmöglichkeit beruht auf dem Reflection-Ansatz. Intern wird immer noch der EqualsBuilder verwendet, der mittels clazz.getDeclaredFields()  mit Daten „gefüttert“ wird. Die Implementierung sind dann so aus:

Nun ja. Es ist wirklich sehr übersichtlich und die Implementierung hat die gleichen Nachteile, wie die vorherige Builder-Methode mit dem Unterschied, dass die Performance, aufgrund des Datenzugriffs über Reflections, noch schlechter ist.

Falls aber die Ausführungsgeschwindigkeit keine so große Rolle spielt, hat diese Implementierung einen entscheidenden Vorteil – Stabilität. Auch wenn neue Felder hinzugefügt oder alte entfernt werden, müssen die equals() und hashcode() – Methoden nicht angepasst werden.

Ach ja, es gibt eine Möglichkeit bestimmte Felder auszuschließen. Dazu müssen die Namen der betroffenen Felder wie folgt angegeben werden:

Fazit

Jeder sollte selbst entscheiden, welche Implementierung für das aktuelle Projekt besser ist. Vielleicht macht es Sinn, am Anfang auf die Reflection-Methode zuzugreifen. Erst wenn das Datenmodel fertig und stabil ist, sorgt man für sinnvollere bzw. schnellere Implementierung. Hier ist noch der Performance-Vergleich der drei Methoden:

java hashcode performance

java equals performance

 

Den Quellcode zum Selbst-Ausprobieren findet ihr auf github: example-hashcode-equals-test

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.