Welche Probleme / Fallstricke sind bei der Aufhebung zu beachten? equals
y hashCode
?
Antworten
Zu viele Anzeigen?Die Theorie (für die Sprachjuristen und die mathematisch Interessierten):
equals()
( javadoc ) muss eine Äquivalenzrelation definieren (sie muss reflexiv , symmetrisch y transitiv ). Darüber hinaus muss es sein einheitlich (wenn die Objekte nicht geändert werden, muss er immer denselben Wert zurückgeben). Außerdem, o.equals(null)
muss immer false zurückgeben.
hashCode()
( [javadoc](http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#hashCode()) ) muss auch sein einheitlich (wenn das Objekt nicht im Sinne von equals()
muss er immer denselben Wert zurückgeben).
El Beziehung zwischen den beiden Methoden ist:
Wann immer
a.equals(b)
entoncesa.hashCode()
muss gleich sein mitb.hashCode()
.
In der Praxis:
Wenn Sie das eine außer Kraft setzen, sollten Sie auch das andere außer Kraft setzen.
Verwenden Sie dieselbe Gruppe von Feldern, die Sie für die Berechnung von equals()
zum Berechnen hashCode()
.
Verwenden Sie die ausgezeichneten Helferklassen EqualsBuilder y HashCodeBuilder von der Apache Commons Lang Bibliothek. Ein Beispiel:
public class Person {
private String name;
private int age;
// ...
@Override
public int hashCode() {
return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers
// if deriving: appendSuper(super.hashCode()).
append(name).
append(age).
toHashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person))
return false;
if (obj == this)
return true;
Person rhs = (Person) obj;
return new EqualsBuilder().
// if deriving: appendSuper(super.equals(obj)).
append(name, rhs.name).
append(age, rhs.age).
isEquals();
}
}
Denken Sie auch daran:
Bei Verwendung einer Hash-basierten Sammlung o Karte wie zum Beispiel HashSet , LinkedHashSet , HashMap , Hashtabelle , oder WeakHashMap stellen Sie sicher, dass sich der HashCode() der Schlüsselobjekte, die Sie in die Sammlung aufnehmen, niemals ändert, solange sich das Objekt in der Sammlung befindet. Der sicherste Weg, dies zu gewährleisten, besteht darin, die Schlüssel unveränderlich zu machen, die auch andere Vorteile hat .
Wenn Sie mit Klassen zu tun haben, die mit einem Object-Relationship-Mapper (ORM) wie Hibernate persistiert werden, gibt es einige Probleme zu beachten, falls Sie nicht schon dachten, dass dies unangemessen kompliziert sei!
Faul geladene Objekte sind Unterklassen
Wenn Ihre Objekte mit einem ORM persistiert werden, werden Sie in vielen Fällen mit dynamischen Proxies arbeiten, um ein zu frühes Laden der Objekte aus dem Datenspeicher zu vermeiden. Diese Proxies werden als Unterklassen Ihrer eigenen Klasse implementiert. Dies bedeutet, dass this.getClass() == o.getClass()
wird zurückgegeben false
. Zum Beispiel:
Person saved = new Person("John Doe");
Long key = dao.save(saved);
dao.flush();
Person retrieved = dao.retrieve(key);
saved.getClass().equals(retrieved.getClass()); // Will return false if Person is loaded lazy
Wenn Sie es mit einem ORM zu tun haben, verwenden Sie o instanceof Person
ist das Einzige, das sich korrekt verhält.
Lazy loaded Objekte haben Null-Felder
ORMs verwenden in der Regel die Getter, um das Laden von "lazy loaded" Objekten zu erzwingen. Das bedeutet, dass person.name
wird sein null
wenn person
träge geladen ist, auch wenn person.getName()
erzwingt das Laden und gibt "John Doe" zurück. Meiner Erfahrung nach tritt dies häufiger auf bei hashCode()
y equals()
.
Wenn Sie mit einem ORM arbeiten, stellen Sie sicher, dass Sie immer Getter verwenden und niemals Feldreferenzen in hashCode()
y equals()
.
Durch das Speichern eines Objekts wird sein Zustand geändert
Persistente Objekte verwenden oft eine id
Feld, das den Schlüssel des Objekts enthält. Dieses Feld wird automatisch aktualisiert, wenn ein Objekt zum ersten Mal gespeichert wird. Verwenden Sie kein id-Feld in hashCode()
. Aber Sie können es verwenden in equals()
.
Ein Muster, das ich oft verwende, ist
if (this.getId() == null) {
return this == other;
}
else {
return this.getId().equals(other.getId());
}
Aber: Sie können nicht enthalten getId()
en hashCode()
. Wenn Sie dies tun, wird bei der Persistenz eines Objekts dessen hashCode
Änderungen. Befindet sich das Objekt in einem HashSet
werden Sie es "nie" wieder finden.
In meinem Person
Beispiel, würde ich wahrscheinlich getName()
para hashCode
y getId()
plus getName()
(nur aus Paranoia) für equals()
. Es ist in Ordnung, wenn es ein gewisses Risiko von "Kollisionen" gibt für hashCode()
, aber niemals okay für equals()
.
hashCode()
sollte die sich nicht ändernde Teilmenge der Eigenschaften aus equals()
Eine Klarstellung zum obj.getClass() != getClass()
.
Diese Aussage ist das Ergebnis von equals()
unfreundlich gegenüber dem Erbe zu sein. Die JLS (Java language specification) legt fest, dass, wenn A.equals(B) == true
dann B.equals(A)
muss auch zurückkehren true
. Wenn Sie diese Anweisung weglassen, erben Klassen, die die equals()
(und sein Verhalten ändern) wird diese Spezifikation brechen.
Das folgende Beispiel zeigt, was passiert, wenn die Erklärung weggelassen wird:
class A {
int field1;
A(int field1) {
this.field1 = field1;
}
public boolean equals(Object other) {
return (other != null && other instanceof A && ((A) other).field1 == field1);
}
}
class B extends A {
int field2;
B(int field1, int field2) {
super(field1);
this.field2 = field2;
}
public boolean equals(Object other) {
return (other != null && other instanceof B && ((B)other).field2 == field2 && super.equals(other));
}
}
Tun new A(1).equals(new A(1))
Auch, new B(1,1).equals(new B(1,1))
Das Ergebnis ist richtig, so wie es sein sollte.
Das sieht alles sehr gut aus, aber sehen Sie sich an, was passiert, wenn wir versuchen, beide Klassen zu verwenden:
A a = new A(1);
B b = new B(1,1);
a.equals(b) == true;
b.equals(a) == false;
Das ist natürlich falsch.
Wenn Sie die symmetrische Bedingung sicherstellen wollen. a=b wenn b=a und das Liskov-Substitutionsprinzip rufen Sie super.equals(other)
nicht nur im Fall von B
Instanz, aber prüfen Sie danach auf A
Instanz:
if (other instanceof B )
return (other != null && ((B)other).field2 == field2 && super.equals(other));
if (other instanceof A) return super.equals(other);
else return false;
Das wird ausgegeben:
a.equals(b) == true;
b.equals(a) == true;
Wo, wenn a
ist nicht eine Referenz von B
dann könnte es sich um eine Referenz der Klasse A
(weil Sie es erweitern), in diesem Fall rufen Sie super.equals()
zu .
Eine vererbungsfreundliche Implementierung finden Sie in der Lösung von Tal Cohen, Wie implementiere ich die Methode equals() korrekt?
Zusammenfassung:
In seinem Buch Leitfaden für eine effektive Java-Programmiersprache (Addison-Wesley, 2001) behauptet Joshua Bloch: "Es gibt einfach keine Möglichkeit, eine instanzierbare Klasse zu erweitern und einen Aspekt hinzuzufügen und dabei den Gleichheitsvertrag zu bewahren." Tal ist da anderer Meinung.
Seine Lösung besteht darin, equals() zu implementieren, indem ein anderes unsymmetrisches blindlyEquals() in beide Richtungen aufgerufen wird. blindlyEquals() wird von Unterklassen überschrieben, equals() wird vererbt und niemals überschrieben.
Ejemplo:
class Point {
private int x;
private int y;
protected boolean blindlyEquals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point)o;
return (p.x == this.x && p.y == this.y);
}
public boolean equals(Object o) {
return (this.blindlyEquals(o) && o.blindlyEquals(this));
}
}
class ColorPoint extends Point {
private Color c;
protected boolean blindlyEquals(Object o) {
if (!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint)o;
return (super.blindlyEquals(cp) &&
cp.color == this.color);
}
}
Beachten Sie, dass equals() über Vererbungshierarchien hinweg funktionieren muss, wenn die Liskov-Substitutionsprinzip befriedigt werden soll.
Ich bin immer noch erstaunt, dass niemand die Guava-Bibliothek für diese Aufgabe empfohlen hat.
//Sample taken from a current working project of mine just to illustrate the idea
@Override
public int hashCode(){
return Objects.hashCode(this.getDate(), this.datePattern);
}
@Override
public boolean equals(Object obj){
if ( ! obj instanceof DateAndPattern ) {
return false;
}
return Objects.equal(((DateAndPattern)obj).getDate(), this.getDate())
&& Objects.equal(((DateAndPattern)obj).getDate(), this.getDatePattern());
}
- See previous answers
- Weitere Antworten anzeigen