Welche Probleme / Fallstricke müssen beim Überschreiben berücksichtigt werden equals
und hashCode
?
Welche Probleme / Fallstricke müssen beim Überschreiben berücksichtigt werden equals
und hashCode
?
Antworten:
equals()
( javadoc ) muss eine Äquivalenzbeziehung definieren (sie muss reflexiv , symmetrisch und transitiv sein ). Außerdem muss es konsistent sein (wenn die Objekte nicht geändert werden, muss immer derselbe Wert zurückgegeben werden). Außerdem o.equals(null)
muss immer false zurückgegeben werden.
hashCode()
( javadoc ) muss ebenfalls konsistent sein (wenn das Objekt nicht in Bezug auf geändert wird equals()
, muss es weiterhin denselben Wert zurückgeben).
Die Beziehung zwischen den beiden Methoden ist:
Wann immer
a.equals(b)
, danna.hashCode()
muss das gleiche sein wieb.hashCode()
.
Wenn Sie einen überschreiben, sollten Sie den anderen überschreiben.
Verwenden Sie denselben Feldsatz, den Sie equals()
zum Berechnen verwenden hashCode()
.
Verwenden Sie die hervorragenden Hilfsklassen EqualsBuilder und HashCodeBuilder aus 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();
}
}
Stellen Sie bei Verwendung einer Hash-basierten Sammlung oder Map wie HashSet , LinkedHashSet , HashMap , Hashtable oder WeakHashMap sicher, dass sich der HashCode () der Schlüsselobjekte, die Sie in die Sammlung einfügen , niemals ändert, während sich das Objekt in der Sammlung befindet. Die kugelsichere Möglichkeit, dies sicherzustellen, besteht darin, Ihre Schlüssel unveränderlich zu machen, was auch andere Vorteile bietet .
instanceof
false zurückgegeben wird, wenn der erste Operand null ist (wieder effektives Java).
Es gibt einige Probleme, die Sie beachten sollten, wenn Sie mit Klassen arbeiten, die mit einem Object-Relationship Mapper (ORM) wie Hibernate beibehalten werden, wenn Sie nicht der Meinung sind, dass dies bereits unangemessen kompliziert ist!
Faul geladene Objekte sind Unterklassen
Wenn Ihre Objekte mithilfe eines ORM beibehalten werden, handelt es sich in vielen Fällen um dynamische Proxys, um zu vermeiden, dass Objekte zu früh aus dem Datenspeicher geladen werden. Diese Proxys werden als Unterklassen Ihrer eigenen Klasse implementiert. Dies bedeutet, dass this.getClass() == o.getClass()
zurückkehren wird 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 mit einem ORM zu o instanceof Person
tun haben, ist die Verwendung das einzige, was sich korrekt verhält.
Faul geladene Objekte haben Nullfelder
ORMs verwenden normalerweise die Getter, um das Laden von faul geladenen Objekten zu erzwingen. Dies bedeutet , dass person.name
sein wird , null
wenn person
faul geladen ist, auch wenn person.getName()
Kräfte Laden und kehrt „John Doe“. Nach meiner Erfahrung tritt dies häufiger in hashCode()
und auf equals()
.
Wenn Sie mit einem ORM arbeiten, stellen Sie sicher, dass Sie immer Getter verwenden und niemals Referenzen in hashCode()
und equals()
.
Durch das Speichern eines Objekts wird sein Status geändert
Persistente Objekte verwenden häufig ein id
Feld, um den Schlüssel des Objekts zu halten. 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 in verwenden equals()
.
Ein Muster, das ich oft benutze, ist
if (this.getId() == null) {
return this == other;
}
else {
return this.getId().equals(other.getId());
}
Aber: Sie können nicht getId()
in aufnehmen hashCode()
. Wenn Sie dies tun, werden die hashCode
Änderungen eines Objekts geändert. Wenn sich das Objekt in a befindet HashSet
, werden Sie es "nie" wiederfinden.
In meinem Person
Beispiel würde ich wahrscheinlich getName()
für hashCode
und getId()
plus getName()
(nur für Paranoia) für verwenden equals()
. Es ist in Ordnung, wenn das Risiko von "Kollisionen" besteht hashCode()
, aber niemals in Ordnung equals()
.
hashCode()
sollte die unveränderte Teilmenge der Eigenschaften von verwenden equals()
Saving an object will change it's state
! hashCode
muss zurückkehren int
, also wie werden Sie verwenden getName()
? Können Sie ein Beispiel für IhrehashCode
Eine Klarstellung über die obj.getClass() != getClass()
.
Diese Aussage ist das Ergebnis equals()
einer unfreundlichen Vererbung. Die JLS (Java Language Specification) gibt an, dass wenn A.equals(B) == true
dann B.equals(A)
auch zurückgegeben werden muss true
. Wenn Sie diese Anweisung weglassen, die Klassen erbt, die überschreiben equals()
(und ihr Verhalten ändern), wird diese Spezifikation verletzt.
Betrachten Sie das folgende Beispiel dafür, was passiert, wenn die Anweisung 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 Sie new A(1).equals(new A(1))
auch, new B(1,1).equals(new B(1,1))
Ergebnis geben wahr aus, wie es sollte.
Das sieht alles sehr gut aus, aber schauen Sie, 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;
Offensichtlich ist das falsch.
Wenn Sie den symmetrischen Zustand sicherstellen möchten. a = b wenn b = a und das Liskov-Substitutionsprinzip super.equals(other)
nicht nur im Fall einer B
Instanz aufrufen , sondern nach folgender Prüfung prüfen A
:
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;
Welches wird ausgegeben:
a.equals(b) == true;
b.equals(a) == true;
Wenn a
es sich nicht um eine Referenz von handelt B
, kann es sich auch um eine Referenz der Klasse handeln A
(weil Sie sie erweitern). In diesem Fall rufen Sie super.equals()
auch auf .
ThingWithOptionSetA
gleich a sein kann, Thing
vorausgesetzt, dass alle zusätzlichen Optionen Standardwerte haben, und ebenso für a ThingWithOptionSetB
, sollte es möglich ThingWithOptionSetA
sein, dass a ThingWithOptionSetB
nur dann mit a verglichen wird, wenn alle Nicht-Basiseigenschaften beider Objekte mit ihren Standardeinstellungen übereinstimmen , aber Ich verstehe nicht, wie Sie das testen.
B b2 = new B(1,99)
, dann b.equals(a) == true
und a.equals(b2) == true
aber b.equals(b2) == false
.
Informationen zu einer vererbungsfreundlichen Implementierung finden Sie in der Lösung von Tal Cohen: Wie implementiere ich die equals () -Methode korrekt?
Zusammenfassung:
In seinem Buch Effective Java Programming Language Guide (Addison-Wesley, 2001) behauptet Joshua Bloch: "Es gibt einfach keine Möglichkeit, eine instanziierbare Klasse zu erweitern und einen Aspekt hinzuzufügen, während der gleichwertige Vertrag erhalten bleibt." Tal ist 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 geerbt und niemals überschrieben.
Beispiel:
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 das Liskov-Substitutionsprinzip erfüllt werden soll.
if (this.getClass() != o.getClass()) return false
, aber flexibel, da es nur dann false zurückgibt, wenn sich die abgeleiteten Klassen die Mühe machen, Gleichheit zu ändern. Ist das richtig?
Immer noch erstaunt, dass niemand die Guavenbibliothek dafür 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());
}
this
in this.getDate()
bedeutet nichts (außer Unordnung)
if (!(otherObject instanceof DateAndPattern)) {
. Stimmen Sie Hernan und Steve Kuo zu (obwohl dies eine Frage der persönlichen Präferenz ist), aber trotzdem +1.
In der Superklasse gibt es zwei Methoden: java.lang.Object. Wir müssen sie zu einem benutzerdefinierten Objekt überschreiben.
public boolean equals(Object obj)
public int hashCode()
Gleiche Objekte müssen denselben Hash-Code erzeugen, solange sie gleich sind. Ungleiche Objekte müssen jedoch keine unterschiedlichen Hash-Codes erzeugen.
public class Test
{
private int num;
private String data;
public boolean equals(Object obj)
{
if(this == obj)
return true;
if((obj == null) || (obj.getClass() != this.getClass()))
return false;
// object must be Test at this point
Test test = (Test)obj;
return num == test.num &&
(data == test.data || (data != null && data.equals(test.data)));
}
public int hashCode()
{
int hash = 7;
hash = 31 * hash + num;
hash = 31 * hash + (null == data ? 0 : data.hashCode());
return hash;
}
// other methods
}
Wenn Sie mehr erhalten möchten, überprüfen Sie bitte diesen Link als http://www.javaranch.com/journal/2002/10/equalhash.html
Dies ist ein weiteres Beispiel, http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html
Habe Spaß! @. @
Es gibt verschiedene Möglichkeiten, die Klassengleichheit zu überprüfen, bevor die Mitgliedergleichheit überprüft wird, und ich denke, beide sind unter den richtigen Umständen nützlich.
instanceof
Operator.this.getClass().equals(that.getClass())
.Ich verwende # 1 in einer final
Equals-Implementierung oder bei der Implementierung einer Schnittstelle, die einen Algorithmus für Equals vorschreibt (wie die java.util
Sammlungsschnittstellen - der richtige Weg, um mit (obj instanceof Set)
oder der von Ihnen implementierten Schnittstelle zu prüfen ). Es ist im Allgemeinen eine schlechte Wahl, wenn Gleichheit überschrieben werden kann, da dies die Symmetrieeigenschaft bricht.
Mit Option 2 kann die Klasse sicher erweitert werden, ohne dass Gleichheit überschrieben oder die Symmetrie unterbrochen wird.
Wenn Ihre Klasse auch ist Comparable
, sollten die Methoden equals
und compareTo
auch konsistent sein. Hier ist eine Vorlage für die equals-Methode in einer Comparable
Klasse:
final class MyClass implements Comparable<MyClass>
{
…
@Override
public boolean equals(Object obj)
{
/* If compareTo and equals aren't final, we should check with getClass instead. */
if (!(obj instanceof MyClass))
return false;
return compareTo((MyClass) obj) == 0;
}
}
final
der compareTo()
Fall ist und die Methode zum Umkehren der Sortierreihenfolge überschrieben wurde, sollten Instanzen der Unterklasse und der Oberklasse nicht als gleich angesehen werden. Wenn diese Objekte zusammen in einem Baum verwendet wurden, sind Schlüssel, die gemäß einer instanceof
Implementierung "gleich" waren, möglicherweise nicht auffindbar.
Für Gleichberechtigte schauen Sie in Secrets of Equals von Angelika Langer . Ich liebe es sehr. Sie ist auch eine großartige FAQ über Generika in Java . Sehen Sie sich hier ihre anderen Artikel an (scrollen Sie nach unten zu "Core Java"), wo sie auch mit Teil 2 und "Mixed Type Comparison" fortfährt. Viel Spaß beim Lesen!
Die Methode equals () wird verwendet, um die Gleichheit zweier Objekte zu bestimmen.
da der int-Wert von 10 immer gleich 10 ist. Bei dieser Methode equals () geht es jedoch um die Gleichheit zweier Objekte. Wenn wir Objekt sagen, wird es Eigenschaften haben. Um über die Gleichheit zu entscheiden, werden diese Eigenschaften berücksichtigt. Es ist nicht erforderlich, dass alle Eigenschaften berücksichtigt werden, um die Gleichheit zu bestimmen, und in Bezug auf die Klassendefinition und den Kontext kann entschieden werden. Dann kann die equals () -Methode überschrieben werden.
Wir sollten die hashCode () -Methode immer überschreiben, wenn wir die equals () -Methode überschreiben. Wenn nicht, was wird passieren? Wenn wir in unserer Anwendung Hashtabellen verwenden, verhält sich diese nicht wie erwartet. Da der Hashcode zur Bestimmung der Gleichheit der gespeicherten Werte verwendet wird, gibt er nicht den richtigen entsprechenden Wert für einen Schlüssel zurück.
Die angegebene Standardimplementierung ist die hashCode () -Methode in der Object-Klasse. Sie verwendet die interne Adresse des Objekts und konvertiert sie in eine Ganzzahl und gibt sie zurück.
public class Tiger {
private String color;
private String stripePattern;
private int height;
@Override
public boolean equals(Object object) {
boolean result = false;
if (object == null || object.getClass() != getClass()) {
result = false;
} else {
Tiger tiger = (Tiger) object;
if (this.color == tiger.getColor()
&& this.stripePattern == tiger.getStripePattern()) {
result = true;
}
}
return result;
}
// just omitted null checks
@Override
public int hashCode() {
int hash = 3;
hash = 7 * hash + this.color.hashCode();
hash = 7 * hash + this.stripePattern.hashCode();
return hash;
}
public static void main(String args[]) {
Tiger bengalTiger1 = new Tiger("Yellow", "Dense", 3);
Tiger bengalTiger2 = new Tiger("Yellow", "Dense", 2);
Tiger siberianTiger = new Tiger("White", "Sparse", 4);
System.out.println("bengalTiger1 and bengalTiger2: "
+ bengalTiger1.equals(bengalTiger2));
System.out.println("bengalTiger1 and siberianTiger: "
+ bengalTiger1.equals(siberianTiger));
System.out.println("bengalTiger1 hashCode: " + bengalTiger1.hashCode());
System.out.println("bengalTiger2 hashCode: " + bengalTiger2.hashCode());
System.out.println("siberianTiger hashCode: "
+ siberianTiger.hashCode());
}
public String getColor() {
return color;
}
public String getStripePattern() {
return stripePattern;
}
public Tiger(String color, String stripePattern, int height) {
this.color = color;
this.stripePattern = stripePattern;
this.height = height;
}
}
Beispiel für eine Code-Ausgabe:
bengalTiger1 and bengalTiger2: true
bengalTiger1 and siberianTiger: false
bengalTiger1 hashCode: 1398212510
bengalTiger2 hashCode: 1398212510
siberianTiger hashCode: –1227465966
Ein Fall, den ich gefunden habe, ist, dass zwei Objekte Verweise aufeinander enthalten (ein Beispiel ist eine Eltern-Kind-Beziehung mit einer praktischen Methode für den Elternteil, um alle Kinder zu erhalten).
Diese Art von Dingen ist ziemlich häufig, wenn Sie beispielsweise Zuordnungen im Ruhezustand ausführen.
Wenn Sie beide Enden der Beziehung in Ihren Hashcode aufnehmen oder Tests gleich sind, können Sie in eine rekursive Schleife geraten, die in einer StackOverflowException endet.
Die einfachste Lösung besteht darin, die getChildren-Auflistung nicht in die Methoden aufzunehmen.
equals()
. Wenn ein verrückter Wissenschaftler ein Duplikat von mir erstellen würde, wären wir gleichwertig. Aber wir hätten nicht den gleichen Vater.