Jakie problemy należy wziąć pod uwagę przy nadpisywaniu equals i hashCode w Javie?

Jakie problemy / pułapki należy brać pod uwagę przy nadpisywaniu equals i hashCode?

Author: George Stocker, 2008-08-26

11 answers

Teoria (dla prawników języka i matematycznie pochylonych):

equals() (javadoc ) musi definiować relację równoważności (musi być reflexive, symetryczne i Transitional ). Dodatkowo, musi być spójne (jeśli obiekty nie są modyfikowane, to musi zwracać tę samą wartość). Ponadto o.equals(null) musi zawsze zwracać false.

hashCode() (javadoc) musi być również spójny (jeśli obiekt nie jest modyfikowany w kategoriach equals(), musi zwracać tę samą wartość).

Relacja pomiędzy tymi dwoma metodami wynosi:

ilekroć a.equals(b), to a.hashCode() musi być takie samo jak b.hashCode().

W praktyce:

Jeśli nadpisujesz jeden, to powinieneś nadpisać drugi.

Użyj tego samego zestawu pól, których używasz do obliczenia {[1] } do obliczenia hashCode().

Użyj doskonałych klas pomocniczychEqualsBuilder i HashCodeBuilder z biblioteki Apache Commons Lang. Przykład:

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

Pamiętaj też:

Przy użyciu zbioru hashowego lub Map , np. HashSet, LinkedHashSet, HashMap, Hashtable lub WeakHashMap , upewnij się, że hashCode() kluczowych obiektów umieszczonych w kolekcji nigdy się nie zmienia, gdy obiekt znajduje się w kolekcji. Kuloodporny sposób na zapewnienie ma to na celu uczynienie kluczy niezmiennymi, , co ma również inne zalety .

 1380
Author: Antti Sykäri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-29 19:23:43

Są pewne problemy, które warto zauważyć, jeśli masz do czynienia z klasami, które są utrzymywane przy użyciu Mapera relacji obiektowych (ORM), takiego jak Hibernate, jeśli nie sądziłeś, że to już jest nierozsądnie skomplikowane!

Lazy loaded objects are subclass

Jeśli Twoje obiekty są utrzymywane przy użyciu ORM, w wielu przypadkach będziesz miał do czynienia z dynamicznymi proxy, aby uniknąć zbyt wczesnego ładowania obiektu z magazynu danych. Te proxy są zaimplementowane jako podklasy własnych klasy. Oznacza to, że this.getClass() == o.getClass() zwróci false. Na przykład:

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

Jeśli masz do czynienia z ORM, używanie o instanceof Person jest jedyną rzeczą, która będzie zachowywać się poprawnie.

Lazy loaded objects have null-fields

ORM zwykle używa getterów, aby wymusić Ładowanie leniwych ładowanych obiektów. Oznacza to, że {[5] } będzie null Jeśli {[7] } jest ładowany leniwie, nawet jeśli person.getName() wymusza ładowanie i zwraca "John Doe". Z mojego doświadczenia wynika, że częściej pojawia się to w hashCode() i equals().

Jeśli masz do czynienia z ORM, upewnij się, że zawsze używasz getterów, a nigdy referencji do pól w hashCode() i equals().

Zapisanie obiektu zmieni jego stan

Trwałe obiekty często używają pola id do przechowywania klucza obiektu. To pole zostanie automatycznie zaktualizowane po pierwszym zapisaniu obiektu. Nie używaj pola id w hashCode(). Ale możesz go użyć w equals().

Wzór, którego często używam to

if (this.getId() == null) {
    return this == other;
}
else {
    return this.getId().equals(other.getId());
}

Ale: ty nie może zawierać getId() w hashCode(). Jeśli to zrobisz, gdy obiekt jest utrzymywany, jego hashCode zmienia się. Jeśli obiekt znajduje się w HashSet, "nigdy" go już nie znajdziesz.

W moim Person przykładzie prawdopodobnie użyłbym getName() dla hashCode i getId() plus getName() (tylko dla paranoi) dla equals(). Jest w porządku, jeśli istnieje ryzyko "kolizji" dla hashCode(), ale nigdy nie jest w porządku dla equals().

hashCode() powinien używać nie zmieniającego się podzbioru właściwości z equals()

 281
Author: Johannes Brodwall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-26 11:00:31

Wyjaśnienie o obj.getClass() != getClass().

To stwierdzenie jest wynikiem equals() bycia nieprzyjaznym dziedzictwu. JLS (Java language specification) określa, że if A.equals(B) == true then B.equals(A) musi również zwrócić true. Pominięcie tej instrukcji dziedziczenie klas, które nadpisują equals() (i zmieniają jej zachowanie), spowoduje złamanie tej specyfikacji.

Rozważ następujący przykład, co się dzieje, gdy oświadczenie zostanie pominięte:

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

Robiąc new A(1).equals(new A(1)) również, new B(1,1).equals(new B(1,1)) wynik daje to prawda.

Wygląda to bardzo dobrze, ale zobacz, co się stanie, jeśli spróbujemy użyć obu klas: {]}

A a = new A(1);
B b = new B(1,1);
a.equals(b) == true;
b.equals(a) == false;

Oczywiście, to jest złe.

Jeśli chcesz zapewnić warunek symetryczny. a = B if B = A and the liskov substitution principle call super.equals(other) nie tylko w przypadku instancji B, ale także w przypadku instancji 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;

Który wyświetli:

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

Gdzie, Jeśli a nie jest odniesieniem B, to może być odniesieniem klasy A (ponieważ ją rozszerzasz), w tym przypadku wywołujesz super.equals() too .

 81
Author: Ran Biron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-12 19:54:24

Dla implementacji przyjaznej dziedziczeniu, sprawdź rozwiązanie Tal Cohena, Jak poprawnie zaimplementować metodę equals ()?

Podsumowanie:

Joshua Bloch w swojej książce Effective Java Programming Language Guide (Addison-Wesley, 2001) twierdzi, że " po prostu nie ma sposobu, aby rozszerzyć instancyjną klasę i dodać aspekt, zachowując umowę równości. Tal się nie zgadza.

Jego rozwiązaniem jest zaimplementowanie equals () przez wywołanie innej niesymetrycznej blindlyEquals () blindlyEquals () jest zastępowane przez podklasy, equals () jest dziedziczone i nigdy nie jest zastępowane.

Przykład:

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

Zauważ, że equals() musi działać w hierarchii dziedziczenia, jeśli zasada substytucji Liskowa ma być spełniona.

 42
Author: Kevin Wong,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-30 09:12:57

Wciąż zdumiony, że nikt nie polecił biblioteki guava do tego.

 //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());
    }
 31
Author: Eugene,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-17 19:42:59

Istnieją dwie metody w super klasie jako java.lang.Obiekt. Musimy nadpisać je na obiekt niestandardowy.

public boolean equals(Object obj)
public int hashCode()

Równe obiekty muszą wytwarzać ten sam kod skrótu, o ile są równe, jednak nierówne obiekty nie muszą wytwarzać odrębnych kodów skrótu.

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
}

Jeśli chcesz uzyskać więcej, sprawdź ten link jako http://www.javaranch.com/journal/2002/10/equalhash.html

To jest inny przykład, http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html

Baw Się Dobrze! @.@
 25
Author: Luna Kong,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-20 06:14:09

Jest kilka sposobów, aby sprawdzić równość klas przed sprawdzeniem równości członków, i myślę, że oba są przydatne we właściwych okolicznościach.

  1. użyj operatora instanceof.
  2. Użyj this.getClass().equals(that.getClass()).

Używam #1 w implementacji final equals, lub podczas implementacji interfejsu, który przepisuje algorytm dla equals(jak interfejsy kolekcji java.util-odpowiedni sposób sprawdzenia za pomocą (obj instanceof Set) lub jakiegokolwiek interfejsu, który implementujesz). To ogólnie zły wybór, gdy równe mogą być nadpisane, ponieważ łamie to właściwość symetrii.

Opcja # 2 pozwala na bezpieczne rozszerzenie klasy bez nadpisywania równości lub łamania symetrii.

Jeśli twoja klasa jest również Comparable, metody equals i compareTo powinny być również spójne. Oto szablon dla metody equals w klasie Comparable:

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

}
 18
Author: erickson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-29 16:15:27

Dla równych, spójrz na Secrets of Equals by Angelika Langer . Bardzo mi się podoba. Jest też świetnym FAQ o Generics in Java. Zobacz inne jej artykuły tutaj (przewiń w dół do "Core Java"), gdzie również przechodzi do części 2 i "mixed type comparison". Baw się dobrze czytając je!

 15
Author: Johannes Schaub - litb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-27 22:05:04

Metoda Equals () służy do określenia równości dwóch obiektów.

Ponieważ wartość int 10 jest zawsze równa 10. Ale ta metoda equals() dotyczy równości dwóch obiektów. Kiedy mówimy obiekt, będzie miał właściwości. Aby zdecydować o równości te właściwości są brane pod uwagę. Nie jest konieczne, aby wszystkie właściwości były brane pod uwagę w celu określenia równości i w odniesieniu do definicji klasy i kontekstu można o tym zadecydować. Wtedy metoda equals () może być overridden.

Powinniśmy zawsze nadpisywać metodę hashCode (), gdy tylko nadpisujemy metodę equals (). Jeśli nie, co się stanie? Jeśli użyjemy hashtabli w naszej aplikacji, nie zachowa się ona zgodnie z oczekiwaniami. Ponieważ hashCode jest używany do określania równości przechowywanych wartości, nie zwróci odpowiedniej wartości dla klucza.

Domyślną implementacją jest metoda hashCode () w klasie obiektu używa wewnętrznego adresu obiektu i zamienia go na liczbę całkowitą i zwraca to.

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;

  }
}

Przykładowy Kod Wyjściowy:

bengalTiger1 and bengalTiger2: true 
bengalTiger1 and siberianTiger: false 
bengalTiger1 hashCode: 1398212510 
bengalTiger2 hashCode: 1398212510 
siberianTiger hashCode: –1227465966
 11
Author: rohan kamat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-13 21:27:49

Logicznie mamy:

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

Ale nie vice-versa!

 7
Author: Khaled.K,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-09 15:41:14

Jedną z rzeczy, które znalazłem, jest to, że dwa obiekty zawierają odniesienia do siebie (jeden przykład to relacja rodzic/dziecko z wygodną metodą na rodzica, aby uzyskać wszystkie dzieci).
Tego rodzaju rzeczy są dość powszechne, na przykład podczas mapowania Hibernate.

Jeśli umieścisz oba końce relacji w swoim hashCode lub equals tests, możliwe jest dostanie się do pętli rekurencyjnej, która kończy się odstępem Stoskoverflowexception.
Najprostszym rozwiązaniem jest nie uwzględnianie kolekcja getChildren w metodach.

 6
Author: Darren Greaves,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-02 21:06:47