Apakah ada utilitas refleksi Java untuk melakukan perbandingan mendalam terhadap dua objek?


99

Saya mencoba menulis pengujian unit untuk berbagai clone()operasi di dalam proyek besar dan saya bertanya-tanya apakah ada kelas yang ada di suatu tempat yang mampu mengambil dua objek dengan tipe yang sama, melakukan perbandingan mendalam, dan mengatakan jika mereka identik atau tidak?


1
Bagaimana kelas ini mengetahui apakah pada titik tertentu dari grafik objek ia dapat menerima objek yang identik, atau hanya referensi yang sama?
Zed

Idealnya ini akan cukup dapat dikonfigurasi :) Saya mencari sesuatu yang otomatis sehingga jika field baru ditambahkan (dan tidak digandakan) pengujian dapat mengidentifikasinya.
Uri

3
Apa yang ingin saya katakan adalah bahwa Anda perlu mengkonfigurasi (mengimplementasikan) perbandingan. Jadi mengapa tidak mengganti metode sama dengan di kelas Anda dan menggunakannya?
Zed

3
Jika sama dengan mengembalikan nilai salah untuk objek kompleks yang besar, dari mana Anda memulai? Anda akan lebih baik jika mengubah objek menjadi String multi-baris dan melakukan perbandingan String. Kemudian Anda dapat melihat dengan tepat di mana dua objek berbeda. IntelliJ memunculkan jendela perbandingan "perubahan" yang membantu menemukan banyak perubahan di antara dua hasil, yaitu memahami output assertEquals (string1, string2) dan memberi Anda jendela perbandingan.
Peter Lawrey

Ada beberapa jawaban yang sangat bagus di sini, selain yang diterima, yang tampaknya terkubur
pengguna1445967

Jawaban:


62

Unitils memiliki fungsi ini:

Penegasan kesetaraan melalui refleksi, dengan opsi berbeda seperti mengabaikan nilai default / null Java dan mengabaikan urutan koleksi


9
Saya telah melakukan beberapa pengujian pada fungsi ini dan tampaknya melakukan perbandingan yang mendalam dimana EqualsBuilder tidak.
Howard

Apakah ada cara agar ini tidak mengabaikan bidang transien?
Jepit

@Pinch aku mendengarmu. Saya akan mengatakan alat perbandingan mendalam di unitilscacat justru karena membandingkan variabel bahkan ketika mereka mungkin tidak memiliki dampak yang dapat diamati . Konsekuensi lain (tidak diinginkan) dari membandingkan variabel adalah bahwa penutupan murni (tanpa statusnya sendiri) tidak didukung. Selain itu, ini membutuhkan objek yang dibandingkan dengan jenis runtime yang sama. Saya menyingsingkan lengan baju dan membuat alat perbandingan mendalam versi saya sendiri yang membahas masalah ini.
beluchin

@ Wolfgang adakah contoh kode untuk mengarahkan kita ke? Dari mana Anda menarik kutipan itu?
anon58192932

30

Saya suka pertanyaan ini! Terutama karena hampir tidak pernah dijawab atau dijawab dengan buruk. Sepertinya belum ada yang tahu. Wilayah perawan :)

Pertama, jangan pernah berpikir untuk menggunakan equals. Kontrak equals, sebagaimana didefinisikan dalam javadoc, adalah relasi ekivalen (refleksif, simetris, dan transitif), bukan relasi kesetaraan. Untuk itu, juga harus antisimetris. Satu-satunya implementasi dari equalsitu adalah (atau bisa jadi) hubungan kesetaraan yang sebenarnya adalah yang ada di java.lang.Object. Bahkan jika Anda menggunakannya equalsuntuk membandingkan semua yang ada di grafik, risiko melanggar kontrak cukup tinggi. Seperti yang ditunjukkan Josh Bloch dalam Effective Java , kontrak yang setara sangat mudah diputuskan:

"Tidak ada cara untuk memperluas kelas yang bisa dipakai dan menambahkan aspek sambil mempertahankan kontrak yang sama"

Selain itu, apa gunanya metode boolean bagi Anda? Akan menyenangkan untuk benar-benar merangkum semua perbedaan antara yang asli dan klon, bukan begitu? Selain itu, saya akan berasumsi di sini bahwa Anda tidak ingin direpotkan dengan menulis / memelihara kode perbandingan untuk setiap objek dalam grafik, tetapi Anda sedang mencari sesuatu yang akan diskalakan dengan sumber karena berubah seiring waktu.

Jadi, yang benar-benar Anda inginkan adalah semacam alat perbandingan keadaan. Bagaimana alat itu diterapkan sangat bergantung pada sifat model domain Anda dan batasan kinerja Anda. Menurut pengalaman saya, tidak ada peluru ajaib yang umum. Dan itu akan menjadi lambat pada sejumlah besar iterasi. Tetapi untuk menguji kelengkapan operasi klon, itu akan melakukan pekerjaan dengan cukup baik. Dua pilihan terbaik Anda adalah serialisasi dan refleksi.

Beberapa masalah yang akan Anda temui:

  • Urutan koleksi: Haruskah dua koleksi dianggap serupa jika memiliki objek yang sama, tetapi dalam urutan yang berbeda?
  • Kolom mana yang harus diabaikan: Transient? Statis?
  • Jenis kesetaraan: Haruskah nilai bidang memiliki jenis yang persis sama? Atau apakah boleh bagi yang satu untuk memperpanjang yang lain?
  • Masih ada lagi, tapi aku lupa ...

XStream cukup cepat dan dikombinasikan dengan XMLUnit akan melakukan pekerjaan hanya dalam beberapa baris kode. XMLUnit bagus karena dapat melaporkan semua perbedaan, atau berhenti pada yang pertama ditemukan. Dan hasilnya mencakup xpath ke node yang berbeda, yang bagus. Secara default tidak mengizinkan koleksi yang tidak berurutan, tetapi dapat dikonfigurasi untuk melakukannya. Dengan memasukkan penangan perbedaan khusus (Disebut a DifferenceListener), Anda dapat menentukan cara yang Anda inginkan untuk menangani perbedaan, termasuk mengabaikan urutan. Namun, segera setelah Anda ingin melakukan apa pun selain penyesuaian yang paling sederhana, penulisannya menjadi sulit dan detailnya cenderung terikat pada objek domain tertentu.

Preferensi pribadi saya adalah menggunakan refleksi untuk menggilir semua bidang yang dinyatakan dan menelusuri ke masing-masing, melacak perbedaan saat saya pergi. Kata peringatan: Jangan gunakan rekursi kecuali Anda menyukai pengecualian stack overflow. Jaga hal-hal dalam ruang lingkup dengan tumpukan (gunakan fileLinkedListatau sesuatu). Saya biasanya mengabaikan bidang sementara dan statis, dan saya melewatkan pasangan objek yang telah saya bandingkan, jadi saya tidak berakhir di loop tanpa batas jika seseorang memutuskan untuk menulis kode referensi sendiri (Namun, saya selalu membandingkan pembungkus primitif apa pun yang terjadi. , karena referensi objek yang sama sering digunakan kembali). Anda dapat mengonfigurasi hal-hal di depan untuk mengabaikan pengurutan koleksi dan untuk mengabaikan jenis atau bidang khusus, tetapi saya ingin menentukan kebijakan perbandingan status saya pada bidang itu sendiri melalui penjelasan. Ini, IMHO, adalah persis apa penjelasan yang dimaksudkan untuk, untuk membuat data meta tentang kelas tersedia pada waktu proses. Sesuatu seperti:


@StatePolicy(unordered=true, ignore=false, exactTypesOnly=true)
private List<StringyThing> _mylist;

Saya pikir ini sebenarnya masalah yang sangat sulit, tetapi benar-benar bisa dipecahkan! Dan begitu Anda memiliki sesuatu yang sesuai untuk Anda, itu sangat, sangat, berguna :)

Jadi semoga berhasil. Dan jika Anda menemukan sesuatu yang benar-benar jenius, jangan lupa untuk membagikannya!


15

Lihat DeepEquals dan DeepHashCode () dalam java-util: https://github.com/jdereg/java-util

Kelas ini melakukan apa yang diminta oleh penulis asli.


4
Peringatan: DeepEquals menggunakan metode .equals () dari sebuah objek jika ada. Ini mungkin bukan yang Anda inginkan.
Adam

4
Ini hanya menggunakan .equals () pada kelas jika metode equals () ditambahkan secara eksplisit, jika tidak, ia melakukan perbandingan anggota-oleh-anggota. Logikanya di sini adalah bahwa jika seseorang berusaha untuk menulis metode custom equals () maka itu harus digunakan. Peningkatan masa depan: izinkan flag untuk mengabaikan metode equals () bahkan jika ada. Ada beberapa utilitas yang berguna di java-util, seperti CaseInsensitiveMap / Set.
John DeRegnaucourt

Saya khawatir tentang membandingkan bidang. Perbedaan bidang mungkin tidak dapat diamati dari sudut pandang klien objek dan masih membandingkan mendalam berdasarkan bidang akan menandainya. Selain itu, membandingkan bidang memerlukan objek dari jenis runtime yang sama yang mungkin membatasi.
beluchin

Untuk menjawab @beluchin di atas, DeepEquals.deepEquals () tidak selalu melakukan perbandingan bidang demi bidang. Pertama, ia memiliki opsi untuk menggunakan .equals () pada metode jika ada (yang bukan di Object), atau dapat diabaikan. Kedua, saat membandingkan Peta / Koleksi, ini tidak melihat pada Koleksi atau tipe Peta, maupun bidang pada Koleksi / Peta. Sebaliknya, ini membandingkannya secara logis. Sebuah LinkedHashMap bisa sama dengan TreeMap jika mereka memiliki konten dan elemen yang sama dalam urutan yang sama. Untuk Koleksi dan Peta yang tidak dipesan, hanya item berukuran dan sama dalam yang diperlukan.
John DeRegnaucourt

saat membandingkan Peta / Koleksi, ini tidak melihat pada Koleksi atau jenis Peta, maupun bidang pada Koleksi / Peta. Sebaliknya, ini membandingkan mereka secara logis @JohnDeRegnaucourt, saya akan memperdebatkan perbandingan logis ini yaitu membandingkan hanya apa yang publicharus berlaku untuk semua jenis sebagai lawan hanya berlaku untuk koleksi / peta.
beluchin

10

Override The equals () Metode

Anda cukup mengganti metode equals () kelas menggunakan EqualsBuilder.reflectionEquals () seperti yang dijelaskan di sini :

 public boolean equals(Object obj) {
   return EqualsBuilder.reflectionEquals(this, obj);
 }

7

Hanya harus menerapkan perbandingan dua contoh entitas yang direvisi oleh Hibernate Envers. Saya mulai menulis perbedaan saya sendiri tetapi kemudian menemukan kerangka kerja berikut.

https://github.com/SQiShER/java-object-diff

Anda dapat membandingkan dua objek dengan tipe yang sama dan itu akan menunjukkan perubahan, penambahan dan penghapusan. Jika tidak ada perubahan, maka objeknya sama (secara teori). Anotasi disediakan untuk pengambil yang harus diabaikan selama pemeriksaan. Kerangka kerja memiliki aplikasi yang jauh lebih luas daripada pemeriksaan kesetaraan, yaitu yang saya gunakan untuk menghasilkan log perubahan.

Kinerjanya OK, saat membandingkan entitas JPA, pastikan untuk melepaskannya dari pengelola entitas terlebih dahulu.


6

Saya menggunakan XStream:

/**
 * @see java.lang.Object#equals(java.lang.Object)
 */
@Override
public boolean equals(Object o) {
    XStream xstream = new XStream();
    String oxml = xstream.toXML(o);
    String myxml = xstream.toXML(this);

    return myxml.equals(oxml);
}

/**
 * @see java.lang.Object#hashCode()
 */
@Override
public int hashCode() {
    XStream xstream = new XStream();
    String myxml = xstream.toXML(this);
    return myxml.hashCode();
}

5
Koleksi selain daftar dapat mengembalikan elemen dalam urutan yang berbeda, sehingga perbandingan string akan gagal.
Alexey Berezkin

Juga kelas yang tidak dapat diserialkan akan gagal
Zwelch

6

Di AssertJ , Anda dapat melakukan:

Assertions.assertThat(expectedObject).isEqualToComparingFieldByFieldRecursively(actualObject);

Mungkin itu tidak akan berfungsi di semua kasus, namun akan berfungsi di lebih banyak kasus yang Anda pikirkan.

Inilah yang dikatakan dokumentasinya:

Tegaskan bahwa objek yang diuji (aktual) sama dengan objek yang diberikan berdasarkan rekursif properti / bidang menurut perbandingan properti / bidang (termasuk yang diwariskan). Ini dapat berguna jika implementasi equals aktual tidak cocok untuk Anda. Perbandingan properti / bidang rekursif tidak diterapkan pada bidang yang memiliki penerapan sama dengan kebiasaan, yaitu metode yang diganti sama dengan yang akan digunakan sebagai ganti bidang dengan perbandingan bidang.

Perbandingan rekursif menangani siklus. Secara default, float dibandingkan dengan presisi 1.0E-6 dan ganda dengan 1.0E-15.

Anda dapat menentukan pembanding khusus per bidang (bersarang) atau jenis dengan masing-masing usingComparatorForFields (Comparator, String ...) dan usingComparatorForType (Comparator, Class).

Objek yang akan dibandingkan dapat memiliki tipe yang berbeda tetapi harus memiliki properti / bidang yang sama. Misalnya jika objek sebenarnya memiliki nama bidang String, diharapkan objek lain juga memilikinya. Jika sebuah objek memiliki bidang dan properti dengan nama yang sama, nilai properti akan digunakan di atas bidang.


1
isEqualToComparingFieldByFieldRecursivelysekarang tidak digunakan lagi. Gunakan assertThat(expectedObject).usingRecursiveComparison().isEqualTo(actualObject);sebagai gantinya :)
dargmuesli

5

http://www.unitils.org/tutorial-reflectionassert.html

public class User {

    private long id;
    private String first;
    private String last;

    public User(long id, String first, String last) {
        this.id = id;
        this.first = first;
        this.last = last;
    }
}
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertReflectionEquals(user1, user2);

2
sangat berguna jika Anda harus menangani kelas yang dihasilkan, di mana Anda tidak memiliki pengaruh apa pun tentang yang sederajat!
Matthias B

1
stackoverflow.com/a/1449051/829755 telah menyebutkan ini. Anda harus mengedit pos itu
pengguna829755

1
@ user829755 Dengan cara ini saya kehilangan poin. JADI semua tentang permainan poin)) Orang-orang suka mendapatkan kredit untuk pekerjaan yang dilakukan, saya juga.
gavenkoa

3

Hamcrest memiliki Matcher samePropertyValuesAs . Tapi itu bergantung pada Konvensi JavaBeans (menggunakan getter dan setter). Jika objek yang akan dibandingkan tidak memiliki getter dan setter untuk atributnya, ini tidak akan berfungsi.

import static org.hamcrest.beans.SamePropertyValuesAs.samePropertyValuesAs;
import static org.junit.Assert.assertThat;

import org.junit.Test;

public class UserTest {

    @Test
    public void asfd() {
        User user1 = new User(1, "John", "Doe");
        User user2 = new User(1, "John", "Doe");
        assertThat(user1, samePropertyValuesAs(user2)); // all good

        user2 = new User(1, "John", "Do");
        assertThat(user1, samePropertyValuesAs(user2)); // will fail
    }
}

Kacang pengguna - dengan getter dan setter

public class User {

    private long id;
    private String first;
    private String last;

    public User(long id, String first, String last) {
        this.id = id;
        this.first = first;
        this.last = last;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getFirst() {
        return first;
    }

    public void setFirst(String first) {
        this.first = first;
    }

    public String getLast() {
        return last;
    }

    public void setLast(String last) {
        this.last = last;
    }

}

Ini berfungsi dengan baik sampai Anda memiliki POJO yang menggunakan isFoometode baca untuk sebuah Booleanproperti. Ada PR yang dibuka sejak 2016 untuk memperbaikinya. github.com/hamcrest/JavaHamcrest/pull/136
Snekse

2

Jika objek Anda mengimplementasikan Serializable, Anda dapat menggunakan ini:

public static boolean deepCompare(Object o1, Object o2) {
    try {
        ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
        ObjectOutputStream oos1 = new ObjectOutputStream(baos1);
        oos1.writeObject(o1);
        oos1.close();

        ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
        ObjectOutputStream oos2 = new ObjectOutputStream(baos2);
        oos2.writeObject(o2);
        oos2.close();

        return Arrays.equals(baos1.toByteArray(), baos2.toByteArray());
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

1

Contoh Daftar Tertaut Anda tidak terlalu sulit untuk ditangani. Saat kode melintasi dua grafik objek, ia menempatkan objek yang dikunjungi dalam Set atau Peta. Sebelum melintasi ke referensi objek lain, set ini diuji untuk melihat apakah objek telah dilintasi. Jika demikian, tidak perlu melangkah lebih jauh.

Saya setuju dengan orang di atas yang mengatakan menggunakan LinkedList (seperti Stack tetapi tanpa metode sinkronisasi di atasnya, jadi lebih cepat). Melintasi grafik objek menggunakan Tumpukan, sambil menggunakan refleksi untuk mendapatkan setiap bidang, adalah solusi yang ideal. Ditulis sekali, ini "eksternal" sama dengan () dan "eksternal" hashCode () adalah apa yang harus dipanggil oleh semua metode sama dengan () dan hashCode (). Anda tidak perlu lagi metode pelanggan sama dengan ().

Saya menulis sedikit kode yang melintasi grafik objek lengkap, terdaftar di Google Code. Lihat json-io (http://code.google.com/p/json-io/). Ini membuat serial grafik objek Java menjadi JSON dan deserialisasi darinya. Ini menangani semua objek Java, dengan atau tanpa konstruktor publik, Serializeable atau tidak Serializable, dll. Kode traversal yang sama ini akan menjadi dasar untuk implementasi eksternal "equals ()" dan eksternal "hashcode ()". Btw, JsonReader / JsonWriter (json-io) biasanya lebih cepat daripada ObjectInputStream / ObjectOutputStream bawaan.

JsonReader / JsonWriter ini dapat digunakan untuk perbandingan, tetapi tidak akan membantu dengan kode hash. Jika Anda menginginkan kode hash universal () dan sama dengan (), itu membutuhkan kode itu sendiri. Saya mungkin dapat melakukan ini dengan pengunjung grafik umum. Kita lihat saja nanti.

Pertimbangan lain - bidang statis - itu mudah - mereka dapat dilewati karena semua instance sama dengan () akan memiliki nilai yang sama untuk bidang statis, karena bidang statis dibagikan ke semua instance.

Adapun bidang transien - itu akan menjadi opsi yang dapat dipilih. Terkadang Anda mungkin ingin transien menghitung kali lain tidak. "Terkadang Anda merasa seperti orang gila, terkadang tidak."

Periksa kembali ke proyek json-io (untuk proyek saya yang lain) dan Anda akan menemukan proyek ekuivalen () / hashcode () eksternal. Saya belum punya nama untuk itu, tapi itu akan terlihat jelas.


1

Apache memberi Anda sesuatu, mengonversi kedua objek menjadi string dan membandingkan string, tetapi Anda harus Menimpa toString ()

obj1.toString().equals(obj2.toString())

Ganti toString ()

Jika semua bidang adalah tipe primitif:

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
@Override
public String toString() {return 
ReflectionToStringBuilder.toString(this);}

Jika Anda memiliki bidang dan / atau koleksi dan / atau peta non primitif:

// Within class
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
@Override
public String toString() {return 
ReflectionToStringBuilder.toString(this,new 
MultipleRecursiveToStringStyle());}

// New class extended from Apache ToStringStyle
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.util.*;

public class MultipleRecursiveToStringStyle extends ToStringStyle {
private static final int    INFINITE_DEPTH  = -1;

private int                 maxDepth;

private int                 depth;

public MultipleRecursiveToStringStyle() {
    this(INFINITE_DEPTH);
}

public MultipleRecursiveToStringStyle(int maxDepth) {
    setUseShortClassName(true);
    setUseIdentityHashCode(false);

    this.maxDepth = maxDepth;
}

@Override
protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
    if (value.getClass().getName().startsWith("java.lang.")
            || (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
        buffer.append(value);
    } else {
        depth++;
        buffer.append(ReflectionToStringBuilder.toString(value, this));
        depth--;
    }
}

@Override
protected void appendDetail(StringBuffer buffer, String fieldName, 
Collection<?> coll) {
    for(Object value: coll){
        if (value.getClass().getName().startsWith("java.lang.")
                || (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
            buffer.append(value);
        } else {
            depth++;
            buffer.append(ReflectionToStringBuilder.toString(value, this));
            depth--;
        }
    }
}

@Override
protected void appendDetail(StringBuffer buffer, String fieldName, Map<?, ?> map) {
    for(Map.Entry<?,?> kvEntry: map.entrySet()){
        Object value = kvEntry.getKey();
        if (value.getClass().getName().startsWith("java.lang.")
                || (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
            buffer.append(value);
        } else {
            depth++;
            buffer.append(ReflectionToStringBuilder.toString(value, this));
            depth--;
        }
        value = kvEntry.getValue();
        if (value.getClass().getName().startsWith("java.lang.")
                || (maxDepth != INFINITE_DEPTH && depth >= maxDepth)) {
            buffer.append(value);
        } else {
            depth++;
            buffer.append(ReflectionToStringBuilder.toString(value, this));
            depth--;
        }
    }
}}

0

Saya rasa Anda tahu ini, tetapi secara teori, Anda seharusnya selalu menimpa .equals untuk menyatakan bahwa dua objek benar-benar sama. Ini akan menyiratkan bahwa mereka memeriksa metode .equals yang diganti pada anggotanya.

Hal semacam inilah yang menyebabkan .equals didefinisikan di Object.

Jika ini dilakukan secara konsisten, Anda tidak akan mendapat masalah.


2
Masalahnya adalah saya ingin mengotomatiskan pengujian ini untuk basis kode besar yang ada yang tidak saya tulis ... :)
Uri

0

Jaminan yang terhenti untuk perbandingan yang begitu dalam mungkin menjadi masalah. Apa yang harus dilakukan berikut ini? (Jika Anda menerapkan pembanding seperti itu, ini akan menjadi pengujian unit yang baik.)

LinkedListNode a = new LinkedListNode();
a.next = a;
LinkedListNode b = new LinkedListNode();
b.next = b;

System.out.println(DeepCompare(a, b));

Ini yang lainnya:

LinkedListNode c = new LinkedListNode();
LinkedListNode d = new LinkedListNode();
c.next = d;
d.next = c;

System.out.println(DeepCompare(c, d));

Jika Anda memiliki pertanyaan baru, silakan tanyakan dengan mengklik tombol Ajukan Pertanyaan . Sertakan link ke pertanyaan ini jika membantu memberikan konteks.
YoungHobbit

@younghobbit: tidak, ini bukan pertanyaan baru. Tanda tanya dalam jawaban tidak membuat tanda itu sesuai. Mohon lebih diperhatikan.
Ben Voigt

Dari ini: Using an answer instead of a comment to get a longer limit and better formatting.Jika ini adalah komentar, mengapa menggunakan bagian jawaban? Itulah mengapa saya menandainya. bukan karena ?. Jawaban ini sudah ditandai oleh orang lain, yang tidak meninggalkan komentarnya. Saya baru saja mendapatkan ini di antrean tinjauan. Bisa jadi saya buruk itu, saya seharusnya lebih berhati-hati.
YoungHobbit

0

Saya pikir solusi termudah yang terinspirasi oleh solusi Ray Hulha adalah dengan membuat serial objek dan kemudian membandingkan hasil mentahnya secara mendalam.

Serialisasi dapat berupa byte, json, xml atau toString sederhana, dll. ToString tampaknya lebih murah. Lombok menghasilkan ToSTring gratis yang dapat disesuaikan dan mudah untuk kami. Lihat contoh di bawah ini.

@ToString @Getter @Setter
class foo{
    boolean foo1;
    String  foo2;        
    public boolean deepCompare(Object other) { //for cohesiveness
        return other != null && this.toString().equals(other.toString());
    }
}   

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.