Welche Probleme sollten beim Überschreiben von Equals und HashCode in Java berücksichtigt werden?

StackOverflow https://stackoverflow.com/questions/27581

Frage

Welche Probleme/Fallstricke müssen beim Überschreiben berücksichtigt werden? equals Und hashCode?

War es hilfreich?

Lösung

Die Theorie (für Sprachjuristen und Mathematiker):

equals() (Javadoc) muss eine Äquivalenzrelation definieren (es muss sein reflexiv, symmetrisch, Und transitiv).Darüber hinaus muss es sein konsistent (Wenn die Objekte nicht geändert werden, muss weiterhin derselbe Wert zurückgegeben werden).Außerdem, o.equals(null) muss immer false zurückgeben.

hashCode() (Javadoc) muss auch sein konsistent (wenn das Objekt nicht in Bezug auf geändert wird equals(), es muss immer den gleichen Wert zurückgeben).

Der Beziehung zwischen den beiden Methoden ist:

Wann immer a.equals(b), Dann a.hashCode() muss gleich sein wie b.hashCode().

In der Praxis:

Wenn Sie eines überschreiben, sollten Sie auch das andere überschreiben.

Verwenden Sie denselben Satz von Feldern, den Sie zum Berechnen verwenden equals() berechnen hashCode().

Nutzen Sie die hervorragenden Hilfsklassen EqualsBuilder Und HashCodeBuilder von dem 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 eines Hash-basierten Sammlung oder Karte wie zum Beispiel HashSet, LinkedHashSet, HashMap, Hash-tabelle, oder WeakHashMap, Stellen Sie sicher, dass sich der hashCode() der Schlüsselobjekte, die Sie in die Sammlung einfügen, nie ändert, während sich das Objekt in der Sammlung befindet.Der beste Weg, dies sicherzustellen, besteht darin, Ihre Schlüssel unveränderlich zu machen. was auch andere Vorteile hat.

Andere Tipps

Es gibt einige Probleme, die es zu beachten gilt, wenn Sie mit Klassen arbeiten, die mit einem Object-Relationship Mapper (ORM) wie Hibernate persistiert werden, sofern Sie nicht schon der Meinung sind, dass dies unangemessen kompliziert ist!

Lazy Loaded-Objekte sind Unterklassen

Wenn Ihre Objekte mithilfe eines ORM dauerhaft gespeichert werden, werden Sie es in vielen Fällen mit dynamischen Proxys zu tun haben, um zu vermeiden, dass Objekte zu früh aus dem Datenspeicher geladen werden.Diese Proxys werden als Unterklassen Ihrer eigenen Klasse implementiert.Das bedeutet, dassthis.getClass() == o.getClass() wird zurückkehren 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, was sich richtig verhält.

Lazy Loaded-Objekte haben Nullfelder

ORMs verwenden normalerweise Getter, um das Laden verzögert geladener Objekte zu erzwingen.Das bedeutet, dass person.name wird sein null Wenn person ist Lazy Loaded, auch wenn person.getName() erzwingt das Laden und gibt „John Doe“ zurück.Meiner Erfahrung nach kommt das häufiger vor hashCode() Und equals().

Wenn Sie es mit einem ORM zu tun haben, stellen Sie sicher, dass Sie immer Getter und niemals Feldverweise verwenden hashCode() Und equals().

Durch das Speichern eines Objekts wird sein Status geändert

Permanente Objekte verwenden häufig a id Feld, um den Schlüssel des Objekts zu speichern.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 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 einschließen getId() In hashCode().Wenn Sie dies tun, wird ein Objekt dauerhaft gespeichert hashCode Änderungen.Befindet sich das Objekt in a HashSet, du wirst es „nie wieder“ finden.

In meinem Person Beispiel würde ich wahrscheinlich verwenden getName() für hashCode Und getId() Plus getName() (nur aus Paranoia) für equals().Es ist in Ordnung, wenn ein gewisses Risiko von „Kollisionen“ besteht hashCode(), aber nie okay für equals().

hashCode() sollte die sich nicht ändernde Teilmenge der Eigenschaften von verwenden equals()

Eine Klarstellung zum obj.getClass() != getClass().

Diese Aussage ist das Ergebnis von equals() erbfeindlich sein.Die JLS (Java-Sprachspezifikation) legt fest, dass if A.equals(B) == true Dann B.equals(A) muss auch zurückkehren true.Wenn Sie diese Anweisung weglassen, erben Sie Klassen, die überschreiben equals() (und sein Verhalten ändern) wird diese Spezifikation verletzen.

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 new A(1).equals(new A(1)) Auch, new B(1,1).equals(new B(1,1)) Das Ergebnis wird wahr ausgegeben, 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 aufrufen super.equals(other) nicht nur im Fall von B Beispiel, aber überprüfen Sie danach A Beispiel:

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;

Was wird ausgeben:

a.equals(b) == true;
b.equals(a) == true;

Wo, wenn a ist keine Referenz von B, dann könnte es sich um eine Referenz der Klasse handeln A (weil Sie es erweitern), in diesem Fall rufen Sie an super.equals() zu.

Für eine vererbungsfreundliche Implementierung schauen Sie sich die Lösung von Tal Cohen an: Wie setze ich die Methode equal() richtig um?

Zusammenfassung:

In seinem Buch Effektiver Leitfaden zur Java-Programmiersprache (Addison-Wesley, 2001), Joshua Bloch behauptet, dass "es einfach keine Möglichkeit gibt, eine sofortige Klasse zu erweitern und einen Aspekt hinzuzufügen und gleichzeitig den Gleichen Vertrag zu erhalten." Tal ist anderer Meinung.

Seine Lösung besteht darin, equal() zu implementieren, indem er ein anderes nichtsymmetrisches blindlyEquals() in beide Richtungen aufruft.blindlyEquals() wird von Unterklassen überschrieben, equal() wird vererbt 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 equal() über Vererbungshierarchien hinweg funktionieren muss, wenn die Liskov-Substitutionsprinzip ist zufrieden zu sein.

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

Es gibt zwei Methoden in der Superklasse java.lang.Object.Wir müssen sie in ein benutzerdefiniertes 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, klicken Sie bitte auf diesen Link 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

Viel Spaß!@.@

Es gibt mehrere Möglichkeiten, die Klassengleichheit vor der Mitgliedergleichheit zu prüfen, und ich denke, dass beide unter den richtigen Umständen nützlich sind.

  1. Benutzen Sie die instanceof Operator.
  2. Verwenden this.getClass().equals(that.getClass()).

Ich verwende Nr. 1 in a final Equals-Implementierung oder bei der Implementierung einer Schnittstelle, die einen Algorithmus für Equals vorschreibt (wie die java.util Sammlungsschnittstellen – der richtige Weg, mit zu überprüfen (obj instanceof Set) oder welche Schnittstelle Sie auch immer implementieren).Im Allgemeinen ist es eine schlechte Wahl, wenn Gleichheiten überschrieben werden können, da dadurch die Symmetrieeigenschaft zerstört wird.

Mit Option 2 kann die Klasse sicher erweitert werden, ohne Equals zu überschreiben oder die Symmetrie zu brechen.

Wenn Ihre Klasse auch ist Comparable, Die equals Und compareTo Auch die Methoden sollten konsistent sein.Hier ist eine Vorlage für die Methode equal in a 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;
  }

}

Für Gleichgestellte schauen Sie sich um Geheimnisse der Gleichen von Angelika Langer.Ich liebe es sehr.Sie ist auch eine tolle FAQ zu diesem Thema Generika in Java.Sehen Sie sich ihre anderen Artikel an Hier (nach unten scrollen zu „Core Java“), wo sie auch mit Teil 2 und dem „Vergleich gemischter Typen“ fortfährt.Viel Spaß beim Lesen!

Die Methode equal() wird verwendet, um die Gleichheit zweier Objekte zu bestimmen.

da der int-Wert 10 immer gleich 10 ist.Bei dieser Methode equal() geht es jedoch um die Gleichheit zweier Objekte.Wenn wir Objekt sagen, hat es Eigenschaften.Um über Gleichheit zu entscheiden, werden diese Eigenschaften berücksichtigt.Es ist nicht erforderlich, dass alle Eigenschaften berücksichtigt werden, um die Gleichheit zu bestimmen, und sie kann im Hinblick auf die Klassendefinition und den Kontext entschieden werden.Dann kann die Methode equal() überschrieben werden.

Wir sollten die Methode hashCode() immer überschreiben, wenn wir die Methode equal() überschreiben.Wenn nicht, was wird passieren?Wenn wir in unserer Anwendung Hashtabellen verwenden, verhält sie sich 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 Methode hashCode() in der Klasse Object. Sie verwendet die interne Adresse des Objekts, wandelt sie in eine Ganzzahl um 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;

  }
}

Beispielcode-Ausgabe:

bengalTiger1 and bengalTiger2: true 
bengalTiger1 and siberianTiger: false 
bengalTiger1 hashCode: 1398212510 
bengalTiger2 hashCode: 1398212510 
siberianTiger hashCode: –1227465966

Logischerweise haben wir:

a.getClass().equals(b.getClass()) && a.equals(b)a.hashCode() == b.hashCode()

Aber nicht und umgekehrt!

Ein Fallstrick, den ich gefunden habe, besteht darin, dass zwei Objekte Verweise aufeinander enthalten (ein Beispiel ist eine Eltern-Kind-Beziehung mit einer praktischen Methode für das Elternteil, um alle Kinder abzurufen).
Solche Dinge kommen beispielsweise bei Hibernate-Zuordnungen recht häufig vor.

Wenn Sie beide Enden der Beziehung in Ihren HashCode- oder Equals-Test einbeziehen, kann es zu einer rekursiven Schleife kommen, die in einer StackOverflowException endet.
Die einfachste Lösung besteht darin, die getChildren-Sammlung nicht in die Methoden einzubeziehen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top