616 Stimmen

Welche Aspekte sollten beim Überschreiben von equals und hashCode in Java berücksichtigt werden?

Welche Probleme / Fallstricke sind bei der Aufhebung zu beachten? equals y hashCode ?

1479voto

Antti Kissaniemi Punkte 18474

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) entonces a.hashCode() muss gleich sein mit b.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 .

302voto

Johannes Brodwall Punkte 7354

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()

88voto

Ran Biron Punkte 6199

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 .

47voto

Kevin Wong Punkte 14146

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.

32voto

Eugene Punkte 109680

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());
    }

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X