Apache Commons sama dengan / hashCode builder [ditutup]

155

Saya ingin tahu, apa pendapat orang-orang di sini tentang menggunakan org.apache.commons.lang.builder EqualsBuilder/ HashCodeBuilder untuk mengimplementasikan equals/ hashCode? Apakah ini akan menjadi praktik yang lebih baik daripada menulis sendiri? Apakah ini berfungsi baik dengan Hibernate? Apa pendapatmu?

aug70co
sumber
16
Hanya saja, jangan tergoda oleh reflectionEqualsdan reflectionHashcodefungsi; kinerja adalah pembunuh mutlak.
skaffman
14
Saya melihat beberapa diskusi di sini tentang sama dengan kemarin dan punya waktu luang, jadi saya melakukan tes cepat. Saya punya 4 objek dengan implementasi yang sama dengan yang berbeda. gerhana yang dihasilkan, equalsbuilder.append, equalsbuilder.reflection, dan anotasi pojomatik. Baseline adalah gerhana. equalsbuilder.append mengambil 3.7x. pojomatic mengambil 5x. berbasis refleksi mengambil 25.8x. Itu cukup mengecilkan hati karena saya suka kesederhanaan berdasarkan refleksi dan saya tidak tahan dengan nama "pojomatic".
digitaljoel
5
Pilihan lain adalah Proyek Lombok; ia menggunakan generasi bytecode daripada refleksi, jadi ia harus bekerja sebaik Eclipse. projectlombok.org/features/EqualsAndHashCode.html
Miles

Jawaban:

212

Pembangun commons / lang sangat bagus dan saya telah menggunakan mereka selama bertahun-tahun tanpa overhead kinerja yang nyata (dengan dan tanpa hibernasi). Tetapi seperti yang ditulis Alain, cara Jambu bahkan lebih baik:

Inilah contoh Kacang:

public class Bean{

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

}

Inilah equals () dan hashCode () diimplementasikan dengan Commons / Lang:

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

dan di sini dengan Java 7 atau lebih tinggi (terinspirasi oleh Guava):

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

Catatan: kode ini awalnya merujuk Guava, tetapi seperti yang ditunjukkan oleh komentar, fungsi ini telah diperkenalkan di JDK, jadi Guava tidak lagi diperlukan.

Seperti yang Anda lihat, versi Guava / JDK lebih pendek dan menghindari objek pembantu yang berlebihan. Dalam kasus yang sama, itu bahkan memungkinkan untuk hubungan pendek evaluasi jika Object.equals()panggilan sebelumnya mengembalikan false (agar adil: commons / lang memiliki ObjectUtils.equals(obj1, obj2)metode dengan semantik identik yang dapat digunakan alih-alih EqualsBuildermemungkinkan hubungan pendek seperti di atas).

Jadi: ya, pembangun lang commons lebih disukai daripada yang dibangun secara manual equals()dan hashCode()metode (atau monster mengerikan yang Eclipse akan hasilkan untuk Anda), tetapi versi Java 7+ / Guava bahkan lebih baik.

Dan catatan tentang Hibernate:

hati-hati menggunakan koleksi malas dalam implementasi equals (), hashCode (), dan toString () Anda. Itu akan gagal total jika Anda tidak memiliki Sesi terbuka.


Catatan (kira-kira sama dengan ()):

a) di kedua versi sama dengan () di atas, Anda mungkin ingin menggunakan salah satu atau kedua pintasan ini juga:

@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) tergantung pada interpretasi Anda terhadap kontrak equals (), Anda juga dapat mengubah baris

    if(obj instanceof Bean){

untuk

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

Jika Anda menggunakan versi kedua, Anda mungkin juga ingin memanggil super(equals())di dalam equals()metode Anda . Pendapat berbeda di sini, topiknya dibahas dalam pertanyaan ini:

cara yang benar untuk memasukkan superclass ke dalam implementasi Guava Objects.hashcode ()?

(Meskipun ini tentang hashCode(), hal yang sama berlaku untuk equals())


Catatan (terinspirasi oleh Komentar dari kayahr )

Objects.hashCode(..)(seperti yang mendasarinya Arrays.hashCode(...)) mungkin berkinerja buruk jika Anda memiliki banyak bidang primitif. Dalam kasus seperti itu, EqualsBuildermungkin sebenarnya merupakan solusi yang lebih baik.

Sean Patrick Floyd
sumber
34
Hal yang sama akan dimungkinkan dengan Java 7 Objects.equals: download.oracle.com/javase/7/docs/api/java/util/…
Thomas Jung
3
Jika saya membacanya dengan benar, Josh Bloch mengatakan dalam Effective Java , Item 8, bahwa Anda tidak boleh menggunakan getClass () dalam metode equals () Anda; Anda harus menggunakan instanceof.
Jeff Olson
6
@SeanPatrickFloyd Cara Guava tidak hanya membuat objek array untuk varargs, ia juga mengubah SEMUA parameter menjadi objek. Jadi ketika Anda melewatkan 10 nilai int untuk itu maka Anda akan berakhir dengan 10 objek Integer dan objek array. Solusi commons-lang hanya membuat objek tunggal, tidak peduli berapa banyak nilai yang Anda tambahkan ke kode hash. Masalah yang sama dengan equals. Guava mengubah semua nilai menjadi objek, commons-lang hanya membuat satu objek baru.
kayahr
1
@ Yehee Saya sangat tidak setuju bahwa ini lebih baik. Menggunakan Refleksi untuk menghitung kode hash bukanlah sesuatu yang pernah saya lakukan. Overhead kinerja mungkin diabaikan, tetapi rasanya salah.
Sean Patrick Floyd
1
@kaushik membuat final kelas sebenarnya memecahkan potensi masalah dari kedua versi (instanceof dan getClass ()), selama Anda menerapkan equals Anda () di kelas daun saja
Sean Patrick Floyd
18

Teman-teman, bangun! Karena Java 7 ada metode pembantu untuk equals dan kode hash di perpustakaan standar. Penggunaannya sepenuhnya setara dengan penggunaan metode Guava.

Mikhail Golubtsov
sumber
a) pada saat pertanyaan ini diajukan, Java 7 belum ada di sana b) secara teknis, mereka tidak cukup setara. jdk memiliki metode Objects.equals versus metode Objects.equal Guava. Saya dapat menggunakan impor statis dengan versi Guava saja. Itu hanya kosmetik, saya tahu, tapi itu membuat non-Jambu terasa lebih berantakan.
Sean Patrick Floyd
Ini bukan metode yang baik untuk menimpa objek sama dengan metode karena fakta bahwa Objects.equals akan memanggil metode .equals instance. Jika Anda memanggil Objects.equals dalam metode .equals instance itu akan menyebabkan stack overflow.
dardo
Bisakah Anda memberi contoh, ketika itu jatuh ke dalam satu lingkaran?
Mikhail Golubtsov
OP meminta untuk mengganti metode equals () di dalam sebuah Object. Sesuai dokumentasi metode statis Objects.equals (): "Mengembalikan nilai true jika argumennya sama satu sama lain dan salah jika sebaliknya. Akibatnya, jika kedua argumen itu nol, true dikembalikan dan jika tepat satu argumen nol, false adalah dikembalikan. Jika tidak, kesetaraan ditentukan dengan menggunakan metode equals dari argumen pertama. "Oleh karena itu, jika Anda menggunakan Objects.equals () dalam instance override sama dengan () itu akan memanggil metode itu sendiri equals, lalu Objects.equals () lalu dengan sendirinya lagi, memberikan stack overflow.
dardo
@dardo Kita berbicara tentang penerapan kesetaraan struktural, jadi itu berarti dua objek sama satu sama lain jika bidangnya melakukannya. Lihat contoh Jambu di atas, bagaimana persamaan diimplementasikan.
Mikhail Golubtsov
8

Jika Anda tidak ingin bergantung pada pustaka pihak ke-3 (mungkin Anda menjalankan perangkat dengan sumber daya terbatas) dan Anda bahkan tidak ingin mengetikkan metode Anda sendiri, Anda juga dapat membiarkan IDE melakukan pekerjaan, misalnya dalam penggunaan gerhana

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

Anda akan mendapatkan kode 'asli' yang dapat Anda konfigurasi sesuai keinginan dan yang harus Anda dukung saat perubahan.


Contoh (eclipse 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;
    }

}
FrVaBe
sumber
14
Benar, tetapi kode yang dihasilkan oleh Eclipse tidak dapat dibaca dan tidak dapat dipelihara.
Sean Patrick Floyd
6
Tolong, jangan pernah berpikir tentang sesuatu yang mengerikan seperti yang dihasilkan gerhana equals. Jika Anda tidak ingin bergantung pada pustaka pihak ke-3, maka tulis metode satu baris seperti Objects.equalAnda. Bahkan ketika digunakan hanya sekali atau dua kali, itu membuat kodenya jauh lebih baik!
maaartinus
@maaartinus equals/ hashCodemetode satu baris ???
FrVaBe
1
@maaartinus Guava adalah perpustakaan pihak ke-3. Saya menunjukkan bahwa solusi saya dapat digunakan jika Anda ingin MENGHINDARI menggunakan perpustakaan pihak ke-3.
FrVaBe
1
@FrVaBe: Dan saya menulis "Jika Anda tidak ingin bergantung pada perpustakaan pihak ke-3, maka tulis metode satu baris seperti Objects.equal yourself." Dan kemudian saya menulis metode satu baris yang dapat Anda gunakan untuk MENGHINDARI menggunakan Guava dan masih memotong panjang sama dengan sekitar satu setengah.
maaartinus
6

EqualsBuilder dan HashCodeBuilder memiliki dua aspek utama yang berbeda dari kode yang ditulis secara manual:

  • penanganan nol
  • contoh pembuatan

EqualsBuilder dan HashCodeBuilder memudahkan untuk membandingkan bidang yang bisa menjadi nol. Dengan kode yang ditulis secara manual, ini menghasilkan banyak boilerplate.

Sebaliknya, EqualsBuilder akan membuat instance per metode panggilan yang sama. Jika metode equals Anda sering dipanggil, ini akan membuat banyak contoh.

Untuk Hibernate, implementasi hashCode sama dan tidak ada bedanya. Mereka hanyalah detail implementasi. Untuk hampir semua objek domain yang dimuat dengan hibernate overhead runtime (bahkan tanpa analisis escape) dari Builder dapat diabaikan . Database dan overhead komunikasi akan signifikan.

Seperti yang disebutkan skaffman, versi refleksi tidak dapat digunakan dalam kode produksi. Refleksi akan menjadi lambat dan "implementasi" tidak akan benar untuk semua kecuali kelas yang paling sederhana. Memperhatikan semua anggota juga berbahaya karena anggota yang baru diperkenalkan mengubah perilaku metode yang sama. Versi refleksi dapat bermanfaat dalam kode uji.

Thomas Jung
sumber
Saya tidak setuju bahwa implementasi refleksi "tidak akan benar untuk semua kecuali kelas yang paling sederhana." Dengan pembangun Anda dapat secara eksplisit mengecualikan bidang jika Anda suka, jadi implementasinya sangat tergantung pada definisi kunci bisnis Anda. Sayangnya, saya tidak bisa tidak setuju dengan aspek kinerja implementasi berbasis refleksi.
digitaljoel
1
@ digitalital Ya, Anda dapat mengecualikan bidang, tetapi definisi ini bukan refactoring, simpan. Jadi saya tidak sengaja menyebutkannya.
Thomas Jung
0

Jika Anda hanya berurusan dengan kacang entitas di mana id adalah kunci utama, Anda dapat menyederhanakan.

   @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();
   }
DEREK LEE
sumber
0

Menurut pendapat saya itu tidak cocok dengan Hibernate, terutama contoh dari jawaban yang membandingkan panjang, nama dan anak-anak untuk beberapa entitas. Hibernate menyarankan untuk menggunakan kunci bisnis untuk digunakan dalam equals () dan hashCode (), dan mereka memiliki alasannya. Jika Anda menggunakan generator equals otomatis () dan kode hash () pada kunci bisnis Anda, tidak apa-apa, hanya masalah kinerja yang perlu dipertimbangkan seperti yang disebutkan sebelumnya. Tetapi orang biasanya menggunakan semua properti apa yang IMO sangat salah. Misalnya saya sedang mengerjakan proyek di mana entitas ditulis menggunakan Pojomatic dengan @AutoProperty, apa yang saya anggap sebagai pola yang sangat buruk.

Dua skenario utama mereka untuk menggunakan kode hash () dan equals () adalah:

  • ketika Anda menempatkan instance kelas persisten dalam Set (cara yang disarankan untuk mewakili asosiasi yang bernilai banyak) dan
  • ketika Anda menggunakan reattachment instance yang terpisah

Jadi mari kita asumsikan entitas kita terlihat seperti ini:

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

Keduanya adalah entitas yang sama untuk Hibernate, yang diambil dari beberapa sesi di beberapa titik (id dan kelas / tabelnya sama). Tetapi ketika kita menerapkan auto equals () kode hash () pada semua alat peraga, apa yang kita miliki?

  1. Ketika Anda menempatkan entitas2 ke set persisten di mana entitas1 sudah ada, ini akan diletakkan dua kali dan akan menghasilkan pengecualian selama komit.
  2. Jika Anda ingin melampirkan entitas2 terpisah ke sesi, di mana entitas1 sudah ada mereka (mungkin, saya belum menguji ini khususnya) tidak akan digabungkan dengan benar.

Jadi, untuk 99% proyek yang saya buat, kami menggunakan implementasi equals () dan hashCode () yang dituliskan sekali dalam kelas entitas dasar, yang konsisten dengan konsep Hibernate:

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

Untuk entitas sementara saya melakukan hal yang sama seperti yang akan dilakukan Hibernate pada langkah kegigihan, yaitu. Saya menggunakan pertandingan contoh. Untuk objek persisten saya membandingkan kunci unik, yaitu tabel / id (saya tidak pernah menggunakan kunci komposit).

Lukasz Frankowski
sumber
0

Untuk berjaga-jaga, orang lain akan merasa berguna, saya telah datang dengan kelas Helper ini untuk perhitungan kode hash yang menghindari overhead penciptaan objek tambahan yang disebutkan di atas (pada kenyataannya, overhead metode Objects.hash () bahkan lebih besar ketika Anda memiliki pewarisan karena akan membuat array baru di setiap level!).

Contoh penggunaan:

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

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

Pembantu HashCode:

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

Saya telah memperkirakan bahwa 10 adalah jumlah maksimum properti yang masuk akal dalam model domain, jika Anda memiliki lebih banyak, Anda harus memikirkan refactoring dan memperkenalkan lebih banyak kelas daripada mempertahankan tumpukan String dan primitif.

Kekurangannya adalah: tidak berguna jika Anda memiliki primitif dan / atau array yang perlu Anda hash secara mendalam. (Biasanya ini adalah kasus ketika Anda harus berurusan dengan benda datar (transfer) yang berada di luar kendali Anda).

Vlad
sumber