Apache Commons eşittir / hashCode oluşturucu [kapalı]


155

Ben merak ediyorum, kullanma hakkında burada neler insanların düşündüğünden org.apache.commons.lang.builder EqualsBuilder/ HashCodeBuilder uygulamaktan equals/ hashCode? Kendinizinkini yazmaktan daha iyi bir uygulama olabilir mi? Hazırda Beklet ile iyi oynar mı? Senin düşüncen nedir?


16
Sadece reflectionEqualsve reflectionHashcodeişlevleri tarafından ayartmayın ; performans mutlak bir katildir.
skaffman

14
Burada dün eşit olanlarla ilgili bir tartışma gördüm ve boş zamanım oldu, bu yüzden hızlı bir test yaptım. Farklı eşit uygulamalara sahip 4 nesnem vardı. tutulma, equalsbuilder.append, equalsbuilder.reflection ve pojomatik ek açıklamalar. Taban çizgisi tutulma idi. equalsbuilder.append 3.7x aldı. pojomatik 5x aldı. yansıma tabanlı 25.8x aldı. Oldukça cesaret kırıcıydı çünkü yansıma temelinin sadeliğini seviyorum ve "pojomatik" ismine dayanamıyorum.
digitaljoel

5
Başka bir seçenek Project Lombok; yansıma yerine bayt kodu oluşturma kullanır, bu nedenle Eclipse tarafından üretilen kadar iyi performans göstermelidir. projectlombok.org/features/EqualsAndHashCode.html
Miles

Yanıtlar:


212

Müşterekler / lang inşaatçılar büyük ve ben farkedilir performans yükü (hazırda ve olmadan) olmadan yıllardır kullanıyorum. Ama Alain'in yazdığı gibi, Guava yolu daha da güzel:

İşte bir örnek Bean:

public class Bean{

    private String name;
    private int length;
    private List<Bean> children;

}

Commons / Lang ile uygulanan eşittir () ve hashCode ():

@Override
public int hashCode(){
    return new HashCodeBuilder()
        .append(name)
        .append(length)
        .append(children)
        .toHashCode();
}

@Override
public boolean equals(final Object obj){
    if(obj instanceof Bean){
        final Bean other = (Bean) obj;
        return new EqualsBuilder()
            .append(name, other.name)
            .append(length, other.length)
            .append(children, other.children)
            .isEquals();
    } else{
        return false;
    }
}

ve burada Java 7 veya üstü ile (Guava'dan esinlenilmiştir):

@Override
public int hashCode(){
    return Objects.hash(name, length, children);
}

@Override
public boolean equals(final Object obj){
    if(obj instanceof Bean){
        final Bean other = (Bean) obj;
        return Objects.equals(name, other.name)
            && length == other.length // special handling for primitives
            && Objects.equals(children, other.children);
    } else{
        return false;
    }
}

Not: Bu kod başlangıçta Guava'ya atıfta bulundu, ancak yorumların belirttiği gibi, bu işlevsellik o zamandan beri JDK'da tanıtıldı, bu nedenle Guava artık gerekli değildir.

Gördüğünüz gibi Guava / JDK sürümü daha kısadır ve gereksiz yardımcı nesneleri önler. Eşit olması durumunda, daha önceki bir Object.equals()çağrı yanlış döndürürse değerlendirmenin kısa devre yapılmasına bile izin verir (adil olmak için: commons / lang, yukarıdaki gibi kısa devreye izin vermek ObjectUtils.equals(obj1, obj2)yerine kullanılabilecek aynı anlamsallığa sahip bir yönteme sahiptir EqualsBuilder).

Yani: evet, commons lang inşaatçılar manuel olarak inşa edilmiş equals()ve hashCode()yöntemlere (veya Eclipse'nin sizin için üreteceği korkunç canavarlara) göre çok tercih edilir , ancak Java 7+ / Guava sürümleri daha da iyidir.

Hazırda Bekletme hakkında bir not:

equals (), hashCode () ve toString () uygulamalarınızda tembel koleksiyonları kullanma konusunda dikkatli olun. Açık bir Oturumunuz yoksa, bu perişan bir şekilde başarısız olacaktır.


Not (yaklaşık olarak eşittir ()):

a) yukarıdaki eşittir () sürümlerinin her ikisinde de bu kısayollardan birini veya her ikisini de kullanmak isteyebilirsiniz:

@Override
public boolean equals(final Object obj){
    if(obj == this) return true;  // test for reference equality
    if(obj == null) return false; // test for null
    // continue as above

b) eşittir () sözleşmesini yorumlamanıza bağlı olarak, satır (lar) ı da değiştirebilirsiniz.

    if(obj instanceof Bean){

için

    // make sure you run a null check before this
    if(obj.getClass() == getClass()){ 

İkinci sürümü kullanıyorsanız, muhtemelen yönteminizin super(equals())içinde de aramak istersiniz equals(). Görüşler burada farklı, konu bu soruda tartışılıyor:

Objects.hashcode () uygulamasına süper sınıf eklemenin doğru yolu nedir?

(her ne kadar hakkında olsa da hashCode(), aynı şey geçerlidir equals())


Not ( kayahr'ın yorumundan esinlenmiştir )

Objects.hashCode(..)(temelde olduğu gibi Arrays.hashCode(...)) çok sayıda ilkel alanınız varsa kötü performans gösterebilir. Bu gibi durumlarda, EqualsBuilderaslında daha iyi bir çözüm olabilir.


34
Aynı şey Java 7 Objects.equals için de mümkün olacak: download.oracle.com/javase/7/docs/api/java/util/…
Thomas Jung

3
Doğru okuyorsam Josh Bloch, Effective Java , Item 8'de, equals () yönteminizde getClass () yöntemini kullanmamanız gerektiğini söylüyor; bunun yerine instanceof kullanmalısınız.
Jeff Olson

6
@SeanPatrickFloyd Guava-yolu sadece varargs için bir dizi nesnesi oluşturmakla kalmaz, aynı zamanda TÜM parametreleri nesnelere dönüştürür. Böylece 10 int değeri ilettiğinizde 10 Integer nesnesi ve bir dizi nesnesi elde edersiniz. Commons-lang çözümü, karma koduna kaç değer ekleseniz de, yalnızca tek bir nesne oluşturur. Aynı sorun equals. Guava tüm değerleri nesnelere dönüştürür, commons-lang sadece tek bir yeni nesne oluşturur.
kayahr

1
@wonhee Bunun daha iyi olduğuna kesinlikle katılmıyorum. Karma kodları hesaplamak için Yansıma kullanmak şimdiye kadar yaptığım bir şey değil. Performans yükü muhtemelen ihmal edilebilir, ancak yanlış geliyor.
Sean Patrick Floyd

1
@kaushik, bir sınıf finali yapmak, her iki sürümün de potansiyel sorunlarını çözer (instanceof ve getClass ()), equals ()
Sean Patrick Floyd

18

Millet, uyan! Java 7'den beri standart kütüphanede eşittir ve hashCode için yardımcı yöntemler vardır . Kullanımları Guava yöntemlerinin kullanımına tamamen eşdeğerdir.


a) bu soru sorulduğunda, Java 7 henüz orada değildi b) teknik olarak, tam olarak eşdeğer değillerdir. jdk, Guava'nın Objects.equal yöntemlerine karşı Objects.equals yöntemine sahiptir. Statik içe aktarma işlemlerini yalnızca Guava'nın sürümü ile kullanabilirim. Bu sadece kozmetik, biliyorum, ama Guava olmayanı daha belirgin hale getiriyor.
Sean Patrick Floyd

Bu, Objects.equals öğesinin örneğin .equals yöntemini çağırması nedeniyle bir nesneye eşittir yöntemini geçersiz kılmak için iyi bir yöntem değildir. Örneğin nesnelerinin .equals yöntemi içinde Objects.equals öğesini çağırırsanız, yığın taşmasına neden olur.
dardo

Bir döngüye girdiğinde bir örnek verebilir misiniz?
Mikhail Golubtsov

OP, bir Object içindeki equals () yöntemini geçersiz kılmayı istiyor. Statik yöntemin belgelerine göre Objects.equals (): "Bağımsız değişkenler birbirine eşitse false, aksi takdirde false olur. Sonuç olarak, her iki bağımsız değişken de null ise true döndürülür ve tam olarak bir bağımsız değişken null olursa false olur. döndü. Aksi takdirde, eşitlik ilk argüman yöntemi eşittir kullanılarak belirlenir. "bu nedenle, bunu çağırır) (geçersiz kılındı örneği eşittir içinde Objects.equals () kullanıldığı takdirde kendi eşittir yöntemidir ardından Objects.equals () sonra tekrar bir yığın taşması verir.
dardo

@dardo Yapısal eşitliğin uygulanmasından bahsediyoruz, bu yüzden alanları varsa iki nesnenin birbirine eşit olduğu anlamına gelir . Eşit değerlerin nasıl uygulandığı konusunda yukarıdaki Guava örneğine bakın.
Mikhail Golubtsov

8

Üçüncü taraf bir kütüphaneye bağımlı olmak istemiyorsanız (belki sınırlı kaynaklara sahip bir cihaz çalıştırıyorsanız) ve hatta kendi yöntemlerinizi yazmak istemiyorsanız, IDE'nin işi tutmasına izin verebilirsiniz, örneğin tutulması kullanımında

Source -> Generate hashCode() and equals()...

Sen 'yerli' kodu alırsınız edebilirsiniz istediğiniz gibi yapılandırabilir ve gereken değişiklikleri desteklemektedir.


Örnek (tutulma Juno):

import java.util.Arrays;
import java.util.List;

public class FooBar {

    public String string;
    public List<String> stringList;
    public String[] stringArray;

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((string == null) ? 0 : string.hashCode());
        result = prime * result + Arrays.hashCode(stringArray);
        result = prime * result
                + ((stringList == null) ? 0 : stringList.hashCode());
        return result;
    }
    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        FooBar other = (FooBar) obj;
        if (string == null) {
            if (other.string != null)
                return false;
        } else if (!string.equals(other.string))
            return false;
        if (!Arrays.equals(stringArray, other.stringArray))
            return false;
        if (stringList == null) {
            if (other.stringList != null)
                return false;
        } else if (!stringList.equals(other.stringList))
            return false;
        return true;
    }

}

14
Doğru, ancak Eclipse tarafından üretilen kod okunamıyor ve sürdürülemez.
Sean Patrick Floyd

6
Lütfen, hiçbir zaman tutulma tarafından üretilen kadar korkunç bir şey düşünmeyin equals. 3. taraf kitaplığına bağımlı olmak istemiyorsanız, tek satır yöntemini Objects.equalkendiniz gibi yazın . Sadece bir veya iki kez kullanıldığında bile, kodu daha iyi hale getirir!
maaartinus

@maaartinus equals/ hashCodetek satır yöntemleri ???
17'de FrVaBe

1
@maaartinus Guava üçüncü taraf bir kütüphanedir. Üçüncü taraf kütüphanelerini kullanarak KAÇINMAK istiyorsanız çözümümün kullanılabileceğine dikkat çektim.
FrVaBe

1
@FrVaBe: Ve ben de yazdım "Eğer 3. taraf kitaplığına bağımlı olmak istemiyorsanız, Objects.equal gibi tek satırlık yöntemi kendiniz yazın." Ve sonra Guava kullanarak AVOID için kullanabileceğiniz tek satır yöntemini yazdım ve yine de eşit uzunluğunu yaklaşık yarıya kadar kestim.
maaartinus

6

EqualsBuilder ve HashCodeBuilder, el ile yazılan koddan farklı iki ana özelliğe sahiptir:

  • null kullanım
  • örnek oluşturma

EqualsBuilder ve HashCodeBuilder, boş olabilecek alanları karşılaştırmayı kolaylaştırır. Manuel olarak yazılan kodla, bu çok fazla kazan plakası oluşturur.

EqualsBuilder ise eşittir yöntemi çağrısı başına bir örnek oluşturur. Eşit yöntemleriniz sık sık çağrılırsa, bu çok fazla örnek oluşturur.

Hazırda Beklet için eşittir ve hashCode uygulaması fark etmez. Bunlar sadece bir uygulama detayıdır. Hazırda bekletme ile yüklenen neredeyse tüm etki alanı nesneleri için Oluşturucunun çalışma zamanı yükü (kaçış analizi olmadan bile) yok sayılabilir . Veritabanı ve iletişim yükü önemli olacaktır.

Skaffman'ın belirttiği gibi yansıma versiyonu üretim kodunda kullanılamaz. Yansıma yavaşlamak ve "uygulama" en basit sınıflar dışında herkes için doğru olmayacaktır. Yeni tanıtılan üyeler eşittir yöntem davranışını değiştirdiklerinden, tüm üyeleri hesaba katmak da tehlikelidir. Yansıma sürümü test kodunda faydalı olabilir.


Yansıtma uygulamasının "en basit sınıflar hariç herkes için doğru olmayacağını" kabul etmiyorum. Oluşturucularla, isterseniz alanları açıkça hariç tutabilirsiniz, böylece uygulama gerçekten iş anahtarı tanımınıza bağlıdır. Ne yazık ki, yansıma tabanlı uygulamanın performans yönüne katılmıyorum.
digitaljoel

1
@digitaljoel Evet, alanları hariç tutabilirsiniz, ancak bu tanımlar kaydetmeyi yeniden düzenlemez. Bu yüzden onlardan bilerek bahsetmedim.
Thomas Jung


0

Eğer id'in birincil anahtar olduğu varlık fasulyesi ile uğraşıyorsanız, basitleştirebilirsiniz.

   @Override
   public boolean equals(Object other)
   {
      if (this == other) { return true; }
      if ((other == null) || (other.getClass() != this.getClass())) { return false; }

      EntityBean castOther = (EntityBean) other;
      return new EqualsBuilder().append(this.getId(), castOther.getId()).isEquals();
   }

0

Kanımca Hibernate ile iyi oynamıyor, özellikle de bazı varlıklar için uzunluk, isim ve çocukları karşılaştıran cevaptan örnekler. Hazırda Beklet, equals () ve hashCode () öğelerinde kullanılacak iş anahtarını kullanmanızı önerir ve bunların nedenleri vardır. İş anahtarınızda auto equals () ve hashCode () oluşturucusunu kullanırsanız, sorun değil, yalnızca performans sorunlarının daha önce belirtildiği gibi değerlendirilmesi gerekir. Ancak insanlar genellikle IMO'nun çok yanlış olan tüm özelliklerini kullanırlar. Örneğin şu anda varlıkların @AutoProperty ile Pojomatic kullanılarak yazıldığı proje üzerinde çalışıyorum, gerçekten kötü bir model olarak değerlendiriyorum.

HashCode () ve equals () kullanmak için iki ana senaryosu şunlardır:

  • Kalıcı sınıf örneklerini bir Kümeye koyduğunuzda (çok değerli ilişkilendirmeleri temsil etmenin önerilen yolu) ve
  • bağımsız örneklerin yeniden bağlanmasını kullandığınızda

Varlığımızı şöyle görelim:

class Entity {
  protected Long id;
  protected String someProp;
  public Entity(Long id, String someProp);
}

Entity entity1 = new Entity(1, "a");
Entity entity2 = new Entity(1, "b");

İkisi de Hazırda Bekletme için, aynı anda bir oturumdan getirilen varlıktır (kimlikleri ve sınıfları / tabloları eşittir). Ancak auto equals () işlevini tüm sahne alanlarına hashCode () uyguladığımızda, elimizde ne var?

  1. Varlık2'yi varlık1'in zaten var olduğu kalıcı kümeye koyduğunuzda, bu iki kez konur ve kesinleştirme sırasında istisnaya neden olur.
  2. Ayrılmış varlık2'yi, varlık1'in zaten var olduğu oturuma eklemek istiyorsanız, (muhtemelen bunu özellikle test etmedim) düzgün şekilde birleştirilmez.

Bu nedenle, yaptığım% 99 proje için, Hazırda Bekletme kavramlarıyla tutarlı olan temel varlık sınıfında bir kez yazılan equals () ve hashCode () uygulamasını kullanıyoruz:

@Override
public boolean equals(Object obj) {
    if (StringUtils.isEmpty(id))
        return super.equals(obj);

    return getClass().isInstance(obj) && id.equals(((IDomain) obj).getId());
}

@Override
public int hashCode() {
    return StringUtils.isEmpty(id)
        ? super.hashCode()
        : String.format("%s/%s", getClass().getSimpleName(), getId()).hashCode();
}

Geçici varlık için, Hibernate'in kalıcılık adımında yapacağı şeyi aynısını yaparım, yani. Örnek eşlemeyi kullanıyorum. Kalıcı nesneler için table / id olan benzersiz anahtarı karşılaştırırım (hiçbir zaman bileşik anahtar kullanmam).


0

Her ihtimale karşı, diğerleri yararlı bulacaktır, yukarıda belirtilen ekstra nesne oluşturma yükünü (aslında, Objects.hash () yönteminin yükü daha büyük olduğunda karma kod hesaplaması için bu Yardımcı sınıf ile geldim miras her düzeyde yeni bir dizi oluşturacak gibi!).

Kullanım örneği:

public int hashCode() {
    return HashCode.hash(HashCode.hash(timestampMillis), name, dateOfBirth); // timestampMillis is long
}

public int hashCode() {
    return HashCode.hash(super.hashCode(), occupation, children);
}

HashCode yardımcısı:

public class HashCode {

    public static int hash(Object o1, Object o2) {
        return add(Objects.hashCode(o1), o2);
    }

    public static int hash(Object o1, Object o2, Object o3) {
        return hash(Objects.hashCode(o1), o2, o3);
    }

    ...

    public static int hash(Object o1, Object o2, ..., Object o10) {
        return hash(Objects.hashCode(o1), o2, o3, ..., o10);
    }

    public static int hash(int initial, Object o1, Object o2) {
        return add(add(initial, o1), o2);
    }

    ...

    public static int hash(int initial, Object o1, Object o2, ... Object o10) {
        return add(... add(add(add(initial, o1), o2), o3) ..., o10);
    }

    public static int hash(long value) {
        return (int) (value ^ (value >>> 32));
    }

    public static int hash(int initial, long value) {
        return add(initial, hash(value));
    }

    private static int add(int accumulator, Object o) {
        return 31 * accumulator + Objects.hashCode(o);
    }
}

Bir etki alanı modelinde özelliklerin maksimum makul sayısı olduğunu anladım, eğer daha fazlasına sahipseniz, Dizeleri ve ilkelleri bir yığın korumak yerine yeniden sınıflandırma ve daha fazla sınıf tanıtmayı düşünmelisiniz.

Dezavantajları şunlardır: esas olarak hash etmeniz gereken ilkel ve / veya dizileriniz varsa yararlı değildir. (Normalde, kontrolünüz dışında olan düz (transfer) nesnelerle uğraşmanız gerektiğinde bu durum söz konusudur).

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.