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:
Im Hinblick auf diese Punkte, betrachten wir diese drei Möglichkeiten equals() und hashcode() zu implementieren:
Die Implementierungen werden an folgender sehr einfachen (Kontainer- oder DTO-) Klasse getestet:
public class Person {
private String id;
private String name;
private Integer age;
private String comment;
[...] // getter, setter
}
Normalerweise werden die equals() und hashcode()- Methoden nicht von Hand implementiert, sondern von einer IDE generiert, sodaß es zu solch einem Ergebnis kommt:
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((age == null) ? 0 : age.hashCode());
result = prime * result + ((comment == null) ? 0 : comment.hashCode());
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age == null) {
if (other.age != null)
return false;
} else if (!age.equals(other.age))
return false;
if (comment == null) {
if (other.comment != null)
return false;
} else if (!comment.equals(other.comment))
return false;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
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:
Weitere Möglichkeit die equals() und hashcode() zu implementieren, bietet sich mit Hilfe von HashCodeBuilder und EqualsBuilder aus der Bibliothek apache-lang3
@Override
public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder();
b.append(this.id);
b.append(this.name);
b.append(this.age);
b.append(this.comment);
return b.toHashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof PersonWithBuilder)) {
return false;
}
Person o = (Person)obj;
EqualsBuilder b = new EqualsBuilder();
b.append(this.id, o.id);
b.append(this.name, o.name);
b.append(this.age, o.age);
b.append(this.comment, o.comment);
return b.isEquals();
}
Der größte Vorteil dieser Lösung ist sind Klarheit und Übersichtlichkeit. Mit ein wenig Konfigurationsaufwand ist es auch hier möglich, die equals() und hashcode() mit IDE zu generieren.
Die Nachteile sind aber schwerwiegender:
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
[...]
public EqualsBuilder append(final int lhs, final int rhs) {
if (isEquals == false) {
return this;
}
isEquals = (lhs == rhs);
return this;
}
[...]
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 sieht dann wie folgt aus:
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
@Override
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
Der Code ist dadurch sehr einfach und übersichtlich, allerdings auf Kosten der Performance.
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
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this, "age", "comment");
}
@Override
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this, obj, "age", "comment");
}
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 eine sinnvollere bzw. schnellere Implementierung. Hier ist noch der Performance-Vergleich der drei Methoden:
Den Quellcode zum Selbst-Ausprobieren gibt es auf github: example-hashcode-equals-test