Java'da eşittir ve hashCode geçersiz kılınırken hangi sorunlar dikkate alınmalıdır?


617

Geçersiz kılınırken hangi konular / tuzaklar dikkate alınmalı equalsve hashCode?

Yanıtlar:


1439

Teori (dil avukatları ve matematiksel olarak eğimli):

equals()( javadoc ) bir denklik ilişkisi tanımlamalıdır ( refleksif , simetrik ve geçişli olmalıdır ). Ayrıca, tutarlı olmalıdır (nesneler değiştirilmediyse, aynı değeri döndürmeye devam etmelidir). Ayrıca, o.equals(null)her zaman yanlış döndürmelidir.

hashCode()( javadoc ) da tutarlı olmalıdır (nesne açısından değiştirilmezse equals(), aynı değeri döndürmeye devam etmelidir).

İlişki iki yöntem arasındadır:

Ne zaman olursa a.equals(b), o a.hashCode()zaman aynı olmalıdır b.hashCode().

Uygulamada:

Birini geçersiz kılarsanız, diğerini geçersiz kılmalısınız.

Hesaplamak equals()için hesaplamak için kullandığınız aynı alan kümesini kullanın hashCode().

Mükemmel yardımcı sınıflar kullanın EqualsBuilder ve HashCodeBuilder gelen Apache Commons Lang kütüphanesine. Bir örnek:

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

Ayrıca unutmayın:

HashSet , LinkedHashSet , HashMap , Hashtable veya WeakHashMap gibi karma tabanlı bir Koleksiyon veya Harita kullanırken , koleksiyona koyduğunuz anahtar nesnelerin hashCode () öğesinin nesne koleksiyondayken asla değişmediğinden emin olun. Bunu sağlamanın kurşun geçirmez yolu, anahtarlarınızı değiştirilemez hale getirmektir, bu da başka faydaları da vardır .


12
AppendSuper () ile ilgili ek nokta: yalnızca üst sınıfın eşitlik davranışını devralmak istiyorsanız, hashCode () ve equals () öğelerinde kullanmalısınız. Örneğin, doğrudan Object'ten türetirseniz, tüm Nesneler varsayılan olarak farklı olduğu için bir anlamı yoktur.
Antti Kissaniemi

312
Eclipse'in iki yöntemi sizin için oluşturmasını sağlayabilirsiniz: Source> hashCode () and equals ().
Rok Strniša

27
Netbeans için de aynı şey geçerli: developmentality.wordpress.com/2010/08/24/…
seinecle

6
@Darthenius Eclipse, eşittir bazı durumlarda sorunlara neden olabilecek getClass () kullanır (bkz. Etkili Java öğesi 8)
AndroidGecko

7
instanceofİlk işleneni null olursa false döndüren ilk null denetimi gerekli değildir (yine Etkili Java).
izaban

295

Hazırda Bekleme gibi bir Nesne İlişkisi Eşleştiricisi (ORM) kullanarak devam eden sınıflarla uğraşıyorsanız, bunun zaten mantıksız derecede karmaşık olduğunu düşünmüyorsanız, dikkat çekmeye değer bazı sorunlar var!

Tembel yüklü nesneler alt sınıflardır

Nesneleriniz bir ORM kullanarak kalıcısa, çoğu durumda nesneyi veri deposundan çok erken yüklemekten kaçınmak için dinamik proxy'lerle uğraşacaksınız. Bu proxy'ler kendi sınıfınızın alt sınıfları olarak uygulanır. Bu this.getClass() == o.getClass()geri döneceği anlamına gelir false. Örneğin:

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

Bir ORM ile uğraşıyorsanız, o instanceof Persondoğru davranacak tek şey kullanmaktır.

Tembel yüklenen nesnelerde boş alanlar var

ORM'ler genellikle tembel yüklü nesnelerin yüklenmesini zorlamak için alıcıları kullanır. Bu araçlar person.nameolacak nulleğer person, yüklenen tembel olsa bile person.getName()güçleri yükleme ve iadeler "John Doe". Benim deneyimlerime göre, bu daha sık bitkileri kadar hashCode()ve equals().

Bir ORM beraberler uğraşan Eğer varsa, her zaman alıcılar ve asla saha referansları kullandığınızdan emin olun hashCode()ve equals().

Bir nesneyi kaydetmek durumunu değiştirir

Kalıcı nesneler genellikle idnesnenin anahtarını tutmak için bir alan kullanır. Bir nesne ilk kaydedildiğinde bu alan otomatik olarak güncellenir. Alanında kimlik alanı kullanmayın hashCode(). Ama içinde kullanabilirsiniz equals().

Sık kullandığım bir desen

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

Ama: içeremez getId()içinde hashCode(). Bunu yaparsanız, bir nesne devam ettiğinde hashCodedeğişir. Nesne bir içindeyse, bir HashSetdaha asla bulamazsınız.

Benim içinde Personörneğin, muhtemelen kullanacağı getName()için hashCodeve getId()artı getName()için (sadece paranoya için) equals(). Bazı "çarpışma" riski varsa hashCode(), ama asla iyi değil equals().

hashCode() değişmeyen özellik alt kümesini equals()


2
@Johannes Brodwall: Anlamıyorum Saving an object will change it's state! hashCodedönmeli int, nasıl kullanacaksın getName()? Senin için bir örnek verebilir misinhashCode
jimmybondy

@jimmybondy: getName, kullanılabilecek bir hashCode değerine sahip bir String nesnesi döndürür
mateusz.fiolka

85

Hakkında bir açıklama obj.getClass() != getClass().

Bu ifade, equals()mirasın düşmanca olmasının sonucudur . JLS (Java dil belirtimi), o A.equals(B) == truezaman B.equals(A)dönmesi gerektiğini de belirtir true. Bu ifadeyi geçersiz kılan equals()(ve davranışını değiştiren) sınıfları devralmayı atlarsanız, bu belirtim bozulur.

İfade atlandığında ne olacağına ilişkin aşağıdaki örneği düşünün:

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

Yapma new A(1).equals(new A(1))Ayrıca, new B(1,1).equals(new B(1,1))olması gerektiği gibi, gerçek vermeye sonuçlanır.

Bu çok iyi görünüyor, ancak her iki sınıfı da kullanmaya çalışırsak ne olur bakalım:

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

Açıkçası bu yanlış.

Simetrik koşulu sağlamak istiyorsanız. a = b ise b = a ve Liskov ikame ilkesi super.equals(other)yalnızca Börnek olması durumunda değil, örneğin sonra da kontrol eder 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;

Hangi çıktı:

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

Burada, eğer abir referans değilse B, o zaman sınıfın bir referansı olabilir A(çünkü onu genişletirsiniz), bu durumda siz super.equals() de ararsınız .


2
(Obj.getClass ()! = This.getClass () && obj.getClass (). İsInstance (this) ) dönüş obj.equals (this);
pihentagy

5
@pihentagy - sonra uygulama sınıfı eşittir yöntemi geçersiz kılmadığında bir stackoverflow olsun. eğlenceli değil.
Bi Bi

2
Bir yığın akışı elde edemezsiniz. Eşittir yöntemi geçersiz kılmamışsa, aynı kodu tekrar çağıracaksınız, ancak özyineleme koşulu her zaman yanlış olacaktır!
Jacob Raihle

@pihentagy: İki farklı türetilmiş sınıf varsa bu nasıl davranır? Bir Eğer ThingWithOptionSetAbir eşit olabilir Thingtüm ekstra seçenekler varsayılan değerlere sahip ve aynı şekilde bir için şartıyla ThingWithOptionSetBbir için, o zaman mümkün olmalıdır ThingWithOptionSetAeşit karşılaştırmak üzere bir ThingWithOptionSetBhem tüm nesneleri olmayan baz özellikleri onların varsayılan eşleşen eğer ama Bunu nasıl test ettiğinizi anlamıyorum.
supercat

7
Bununla ilgili sorun, geçişliliği bozmasıdır. Eğer eklerseniz B b2 = new B(1,99)o zaman, b.equals(a) == trueve a.equals(b2) == truefakat b.equals(b2) == false.
nickgrim

46

Kalıtım dostu bir uygulama için, Tal Cohen'in equals () yöntemini nasıl doğru bir şekilde uygularım ?

Özet:

Etkili Java Programlama Dili Kılavuzu (Addison-Wesley, 2001) adlı kitabında Joshua Bloch, "Eşit bir sözleşmeyi genişletmenin ve eşit bir sözleşmeyi korurken bir özellik eklemenin hiçbir yolu olmadığını" iddia ediyor. Tal aynı fikirde değil.

Onun çözümü, simetrik olmayan bir körü körüne her iki şekilde çağırarak equals () yöntemini uygulamaktır. blindlyEquals (), alt sınıflar tarafından geçersiz kılınır, equals () devralınır ve hiçbir zaman geçersiz kılınmaz.

Misal:

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

Liskov İkame İlkesi karşılanacaksa , equals () işlevinin miras hiyerarşileri üzerinde çalışması gerektiğini unutmayın .


10
Burada açıklanan canEqual yöntemine bir göz atın - aynı prensip her iki çözümün de çalışmasını sağlar, ancak canEqual ile aynı alanları iki kez karşılaştırmazsınız (yukarıda, px == this.x her iki yönde de test edilecektir): artima.com /lejava/articles/equality.html
Blaisorblade

2
Her durumda, bunun iyi bir fikir olduğunu düşünmüyorum. Eşittir sözleşmesini gereksiz yere kafa karıştırır - a ve b olmak üzere iki Nokta parametresi alan biri, a.getX () == b.getX () ve a.getY () == b.getY olasılığının farkında olmalıdır () doğru olabilir, ancak a.equals (b) ve b.equals (a) ikisi de yanlış olur (yalnızca bir ColorPoint ise).
Kevin

Temel olarak, bu türdendir, if (this.getClass() != o.getClass()) return falseancak esnektir, çünkü türetilmiş sınıf (lar) eşittirse uğraştığında false değerini döndürür. Bu doğru mu?
Aleksandr Dubinsky

31

Hala hiçbiri bunun için guava kütüphanesini tavsiye etmediğini şaşırttı.

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

23
java.util.Objects.hash () ve java.util.Objects.equals (), Java 7'nin (2011'de piyasaya sürüldü) bir parçasıdır ve bu nedenle bunun için Guava'ya ihtiyacınız yoktur.
Herman

1
Tabii ki, Oracle artık Java 6 için genel güncellemeler sağlamadığı için bundan kaçınmalısınız (Şubat 2013'ten beri durum böyle olmuştur).
Herman

6
Senin thisiçinde this.getDate()hiçbir şey ifade etmiyor (dağınıklık dışında)
Steve Kuo

1
Sizin "değil instanceof" ifadesi fazladan parantez gerekir: if (!(otherObject instanceof DateAndPattern)) {. Fıtık ve Steve Kuo ile anlaşın (bu kişisel tercih meselesi olsa da), ancak yine de +1.
Amos M. Carpenter

26

Süper sınıfta java.lang.Object olarak iki yöntem vardır. Onları özel nesneye geçersiz kılmalıyız.

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

Eşit nesneler, eşit oldukları sürece aynı karma kodunu üretmelidir, ancak eşit olmayan nesnelerin farklı karma kodları üretmesi gerekmez.

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
}

Daha fazlasını edinmek istiyorsanız, lütfen bu bağlantıyı http://www.javaranch.com/journal/2002/10/equalhash.html olarak kontrol edin.

Bu başka bir örnektir, http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html

İyi eğlenceler! @ @.


Üzgünüz ama hashCode yöntemi hakkında bu ifadeyi anlamıyorum: equals () 'den daha fazla değişken kullanıyorsa yasal değildir. Ancak daha fazla değişkenle kod yazarsam kodum derlenir. Neden yasal değil?
Adryr83

19

Üye eşitliğini kontrol etmeden önce sınıf eşitliğini kontrol etmenin birkaç yolu vardır ve her ikisinin de doğru koşullarda yararlı olduğunu düşünüyorum.

  1. instanceofOperatörü kullanın .
  2. Kullanın this.getClass().equals(that.getClass()).

finalEşit bir uygulamada veya eşitler için bir algoritma reçete eden bir arabirim uygularken # 1 kullanıyorum ( java.utilkoleksiyon arabirimleri gibi - kontrol etmenin doğru yolu (obj instanceof Set)veya uyguladığınız arabirim gibi). Eşit değerlerin geçersiz kılınabilmesi genellikle kötü bir seçimdir, çünkü bu simetri özelliğini bozar.

Seçenek # 2, eşitliği veya simetriyi bozmadan sınıfın güvenli bir şekilde genişletilmesini sağlar.

Sınıfınız da Comparableise equalsve compareToyöntemleri de tutarlı olmalıdır. İşte bir Comparablesınıftaki eşittir yöntemi için bir şablon :

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

}

1
Bunun için +1. Ne getClass () ne de instanceof her derde deva değildir ve bu her ikisine de nasıl yaklaşılacağının iyi bir açıklamasıdır. Equals () kullanmak yerine this.getClass () == that.getClass () yapmamak için herhangi bir neden olduğunu düşünmeyin.
Paul Cantrell

Bununla ilgili bir sorun var. Herhangi bir özellik eklemeyen veya equals yöntemini geçersiz kılmayan anonim sınıflar, eşit olmalarına rağmen getClass denetiminde başarısız olur.
steinybot

@Steiny Bana göre farklı türdeki nesnelerin eşit olması gerektiği açık değil; Bir arabirimin farklı uygulamalarını ortak bir anonim sınıf olarak düşünüyorum. Öncülünü desteklemek için bir örnek verebilir misin?
erickson

Sınıfım a = yeni Sınıfım (123); Sınıfım b = yeni Sınıfım (123) {// Bazı yöntemleri geçersiz kıl}; // a.equals (b), this.getClass () kullanılırken false olur. eşittir (that.getClass ())
steinybot

1
@Steiny Sağ. Çoğu durumda olması gerektiği gibi, özellikle eklenmek yerine bir yöntem geçersiz kılınırsa. Yukarıdaki örneğimi düşünün. Değilse ve sıralama düzenini tersine çevirmek finaliçin compareTo()yöntem geçersiz kılındıysa, alt sınıf ve üst sınıf örnekleri eşit kabul edilmemelidir. Bu nesneler bir ağaçta birlikte kullanıldığında, bir instanceofuygulamaya göre "eşit" olan anahtarlar katlanamayabilir.
erickson


11

iki nesnenin eşitliğini belirlemek için equals () yöntemi kullanılır.

int değeri 10 her zaman 10'a eşittir. Ancak bu equals () yöntemi iki nesnenin eşitliği ile ilgilidir. Nesne dediğimizde, özellikleri olacaktır. Eşitlik konusunda karar vermek için bu özellikler dikkate alınır. Eşitliği belirlemek için tüm özelliklerin göz önünde bulundurulması gerekli değildir ve sınıf tanımına ve bağlamına göre karar verilebilir. Sonra equals () yöntemi geçersiz kılınabilir.

equals () yöntemini geçersiz kıldığımızda her zaman hashCode () yöntemini geçersiz kılmalıyız. Değilse ne olacak? Uygulamamızda hashtables kullanırsak, beklendiği gibi davranmaz. HashCode, depolanan değerlerin eşitliğini belirlemede kullanıldığından, bir anahtar için doğru karşılık gelen değeri döndürmez.

Verilen varsayılan uygulama Object sınıfındaki hashCode () yöntemidir, nesnenin dahili adresini kullanır ve onu tamsayıya dönüştürür ve döndürür.

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;

  }
}

Örnek Kod Çıkışı:

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

7

Mantıken var:

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

Ama tam tersi değil !


6

Bulduğum bir gotcha, iki nesnenin birbirine referanslar içerdiği yerdir (bir örnek, tüm çocukları almak için ebeveyn üzerinde bir kolaylık yöntemi ile bir ebeveyn / çocuk ilişkisi).
Bu tür şeyler, örneğin Hazırda Bekletme eşleştirmeleri yaparken oldukça yaygındır.

HashCode'unuza ilişkinin her iki ucunu da eklerseniz veya eşittir sınaması yaparsanız, bir StackOverflowException ile biten yinelemeli bir döngüye girmeniz mümkündür.
En basit çözüm yöntemlere getChildren koleksiyonunu dahil etmemektir.


5
Bence buradaki temel teori , bir nesnenin nitelikleri , kümeleri ve ilişkilendirmeleri arasında ayrım yapmaktır. Asssociations katılmamalıdır equals(). Çılgın bir bilim adamı benim bir kopyamı yaratsaydı, eşdeğer olurduk. Ama aynı babamız olmazdı.
Raedwald
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.