JavaRush /Blog Java /Random-MS /Equals dan kontrak hashCode atau apa sahaja ia
Aleksandr Zimin
Tahap
Санкт-Петербург

Equals dan kontrak hashCode atau apa sahaja ia

Diterbitkan dalam kumpulan
Sebilangan besar pengaturcara Java, sudah tentu, tahu bahawa kaedah equalsberkait hashCoderapat antara satu sama lain, dan dinasihatkan untuk mengatasi kedua-dua kaedah ini dalam kelas mereka secara konsisten. Sebilangan kecil yang sedikit tahu mengapa ini berlaku dan apakah akibat yang menyedihkan yang boleh berlaku jika peraturan ini dilanggar. Saya bercadang untuk mempertimbangkan konsep kaedah ini, ulangi tujuan mereka dan faham mengapa ia sangat berkaitan. Saya menulis artikel ini, seperti artikel sebelumnya tentang memuatkan kelas, untuk diri saya sendiri agar akhirnya mendedahkan semua butiran isu dan tidak lagi kembali kepada sumber pihak ketiga. Oleh itu, saya dengan senang hati menerima kritikan yang membina, kerana jika terdapat jurang di suatu tempat, ia harus dihapuskan. Artikel itu, sayangnya, ternyata agak panjang.

sama dengan peraturan mengatasi

Kaedah equals()diperlukan dalam Java untuk mengesahkan atau menafikan fakta bahawa dua objek dari asal yang sama secara logiknya sama . Iaitu, apabila membandingkan dua objek, pengaturcara perlu memahami sama ada medan pentingnya adalah setara . Tidak semestinya semua medan mestilah sama, kerana kaedah tersebut equals()membayangkan kesamaan logik . Tetapi kadang-kadang tidak ada keperluan khusus untuk menggunakan kaedah ini. Seperti yang mereka katakan, cara paling mudah untuk mengelakkan masalah menggunakan mekanisme tertentu adalah dengan tidak menggunakannya. Perlu diingatkan juga bahawa sebaik sahaja anda memecahkan kontrak, equalsanda kehilangan kawalan untuk memahami cara objek dan struktur lain akan berinteraksi dengan objek anda. Dan seterusnya mencari punca ralat akan menjadi sangat sukar.

Bila tidak boleh mengatasi kaedah ini

  • Apabila setiap contoh kelas adalah unik.
  • Pada tahap yang lebih besar, ini terpakai kepada kelas yang menyediakan gelagat khusus dan bukannya direka bentuk untuk berfungsi dengan data. Seperti, sebagai contoh, sebagai kelas Thread. Bagi mereka equals, pelaksanaan kaedah yang disediakan oleh kelas Objectadalah lebih daripada mencukupi. Contoh lain ialah kelas enum ( Enum).
  • Sebenarnya kelas tidak diperlukan untuk menentukan kesetaraan kejadiannya.
  • Sebagai contoh, untuk kelas java.util.Randomtidak perlu sama sekali untuk membandingkan kejadian kelas antara satu sama lain, menentukan sama ada mereka boleh mengembalikan urutan nombor rawak yang sama. Semata-mata kerana sifat kelas ini tidak menunjukkan kelakuan sedemikian.
  • Apabila kelas yang anda lanjutkan sudah mempunyai pelaksanaan kaedahnya sendiri equalsdan tingkah laku pelaksanaan ini sesuai dengan anda.
  • Sebagai contoh, untuk kelas Set, List, Mappelaksanaan equalsadalah dalam AbstractSet, AbstractListdan AbstractMapmasing-masing.
  • Dan akhirnya, tidak perlu untuk mengatasi equalsapabila skop kelas anda adalah privateatau package-privatedan anda pasti bahawa kaedah ini tidak akan dipanggil.

sama dengan kontrak

Apabila mengatasi kaedah, equalspembangun mesti mematuhi peraturan asas yang ditakrifkan dalam spesifikasi bahasa Java.
  • Reflekstiviti
  • untuk sebarang nilai tertentu x, ungkapan x.equals(x)mesti kembali true.
    Memandangkan - maksud sedemikianx != null
  • simetri
  • untuk mana-mana nilai yang diberikan xdan y, x.equals(y)harus kembali truehanya jika ia y.equals(x)kembali true.
  • Transitiviti
  • untuk mana-mana nilai yang diberikan x, ydan z, jika x.equals(y)pulangan truedan y.equals(z)pulangan true, x.equals(z)mesti mengembalikan nilai tersebut true.
  • Konsisten
  • untuk sebarang nilai yang diberikan, xdan ypanggilan berulang x.equals(y)akan mengembalikan nilai panggilan sebelumnya kepada kaedah ini, dengan syarat medan yang digunakan untuk membandingkan dua objek tidak berubah antara panggilan.
  • Perbandingan null
  • untuk sebarang nilai tertentu xpanggilan x.equals(null)mesti kembali false.

sama dengan pelanggaran kontrak

Banyak kelas, seperti dari Rangka Kerja Koleksi Java, bergantung pada pelaksanaan kaedah equals(), jadi anda tidak boleh mengabaikannya, kerana Pelanggaran kontrak kaedah ini boleh membawa kepada operasi aplikasi yang tidak rasional, dan dalam kes ini agak sukar untuk mencari sebabnya. Mengikut prinsip reflekstiviti , setiap objek mestilah setara dengan dirinya sendiri. Jika prinsip ini dilanggar, apabila kita menambah objek pada koleksi dan kemudian mencarinya menggunakan kaedah, contains()kita tidak akan dapat mencari objek yang baru kita tambahkan pada koleksi. Keadaan simetri menyatakan bahawa mana-mana dua objek mestilah sama tanpa mengira susunan di mana ia dibandingkan. Sebagai contoh, jika anda mempunyai kelas yang mengandungi hanya satu medan jenis rentetan, adalah tidak betul untuk membandingkan equalsmedan ini dengan rentetan dalam kaedah. Kerana dalam kes perbandingan terbalik, kaedah akan sentiasa mengembalikan nilai false.
// Нарушение симметричности
public class SomeStringify {
    private String s;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o instanceof SomeStringify) {
            return s.equals(((SomeStringify) o).s);
        }
        // нарушение симметричности, классы разного происхождения
        if (o instanceof String) {
            return s.equals(o);
        }
        return false;
    }
}
//Правильное определение метода equals
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    return o instanceof SomeStringify &&
            ((SomeStringify) o).s.equals(s);
}
Daripada keadaan transitiviti ia mengikuti bahawa jika mana-mana dua daripada tiga objek adalah sama, maka dalam kes ini ketiga-tiganya mestilah sama. Prinsip ini boleh dilanggar dengan mudah apabila perlu melanjutkan kelas asas tertentu dengan menambahkan komponen yang bermakna padanya . Sebagai contoh, ke kelas Pointdengan koordinat xdan yanda perlu menambah warna titik dengan mengembangkannya. Untuk melakukan ini, anda perlu mengisytiharkan kelas ColorPointdengan medan yang sesuai color. Oleh itu, jika dalam kelas lanjutan kita memanggil equalskaedah induk, dan dalam induk kita menganggap bahawa hanya koordinat xdan dibandingkan y, maka dua titik warna yang berbeza tetapi dengan koordinat yang sama akan dianggap sama, yang tidak betul. Dalam kes ini, adalah perlu untuk mengajar kelas yang diperolehi untuk membezakan warna. Untuk melakukan ini, anda boleh menggunakan dua kaedah. Tetapi seseorang akan melanggar peraturan simetri , dan kedua - transitivity .
// Первый способ, нарушая симметричность
// Метод переопределен в классе ColorPoint
@Override
public boolean equals(Object o) {
    if (!(o instanceof ColorPoint)) return false;
    return super.equals(o) && ((ColorPoint) o).color == color;
}
Dalam kes ini, panggilan point.equals(colorPoint)akan mengembalikan nilai true, dan perbandingan colorPoint.equals(point)akan kembali false, kerana menjangkakan objek kelas "nya". Oleh itu, peraturan simetri dilanggar. Kaedah kedua melibatkan melakukan semakan "buta" dalam kes apabila tiada data tentang warna titik, iaitu kita mempunyai kelas Point. Atau semak warna jika maklumat mengenainya tersedia, iaitu, bandingkan objek kelas ColorPoint.
// Метод переопределен в классе ColorPoint
@Override
public boolean equals(Object o) {
    if (!(o instanceof Point)) return false;

    // Слепая проверка
    if (!(o instanceof ColorPoint))
        return super.equals(o);

    // Полная проверка, включая цвет точки
    return super.equals(o) && ((ColorPoint) o).color == color;
}
Prinsip transitivity dilanggar di sini seperti berikut. Katakan terdapat definisi bagi objek berikut:
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
Oleh itu, walaupun kesamaan p1.equals(p2)dan berpuas hati p2.equals(p3), p1.equals(p3)ia akan mengembalikan nilai false. Pada masa yang sama, kaedah kedua, pada pendapat saya, kelihatan kurang menarik, kerana Dalam sesetengah kes, algoritma mungkin buta dan tidak melakukan perbandingan sepenuhnya, dan anda mungkin tidak mengetahuinya. Sedikit puisi Secara umum, seperti yang saya fahami, tidak ada penyelesaian konkrit untuk masalah ini. Terdapat pendapat daripada seorang pengarang berwibawa bernama Kay Horstmann bahawa anda boleh menggantikan penggunaan operator instanceofdengan panggilan kaedah getClass()yang mengembalikan kelas objek dan, sebelum anda mula membandingkan objek itu sendiri, pastikan ia adalah daripada jenis yang sama , dan tidak memberi perhatian kepada fakta asal usul mereka yang sama. Oleh itu, peraturan simetri dan transitiviti akan dipenuhi. Tetapi pada masa yang sama, di seberang barikade berdiri seorang lagi pengarang, tidak kurang dihormati dalam kalangan luas, Joshua Bloch, yang percaya bahawa pendekatan ini melanggar prinsip penggantian Barbara Liskov. Prinsip ini menyatakan bahawa "kod panggilan mesti merawat kelas asas dengan cara yang sama seperti subkelasnya tanpa mengetahuinya . " Dan dalam penyelesaian yang dicadangkan oleh Horstmann, prinsip ini jelas dilanggar, kerana ia bergantung pada pelaksanaannya. Pendek kata, jelas perkara itu gelap. Perlu juga diperhatikan bahawa Horstmann menjelaskan peraturan untuk menggunakan pendekatannya dan menulis dalam bahasa Inggeris yang jelas bahawa anda perlu memutuskan strategi semasa mereka bentuk kelas, dan jika ujian kesamaan akan dijalankan hanya oleh superclass, anda boleh melakukan ini dengan melakukan operasi itu instanceof. Jika tidak, apabila semantik semakan berubah bergantung pada kelas terbitan dan pelaksanaan kaedah perlu dipindahkan ke bawah hierarki, anda mesti menggunakan kaedah getClass(). Joshua Bloch, sebaliknya, mencadangkan untuk meninggalkan warisan dan menggunakan komposisi objek dengan memasukkan ColorPointkelas dalam kelas Pointdan menyediakan kaedah akses asPoint()untuk mendapatkan maklumat secara khusus tentang titik itu. Ini akan mengelakkan melanggar semua peraturan, tetapi, pada pendapat saya, ia akan menjadikan kod lebih sukar untuk difahami. Pilihan ketiga ialah menggunakan penjanaan automatik kaedah sama dengan menggunakan IDE. Idea, dengan cara ini, menghasilkan semula generasi Horstmann, membolehkan anda memilih strategi untuk melaksanakan kaedah dalam superclass atau dalam keturunannya. Akhir sekali, peraturan ketekalan seterusnya menyatakan bahawa walaupun objek xtidak yberubah, memanggilnya semula x.equals(y)mesti mengembalikan nilai yang sama seperti sebelumnya. Peraturan terakhir ialah tiada objek harus sama dengan null. Semuanya jelas di sini null- ini adalah ketidakpastian, adakah objek sama dengan ketidakpastian? Ia tidak jelas, iaitu false.

Algoritma umum untuk menentukan sama

  1. Semak kesamaan rujukan objek thisdan parameter kaedah o.
    if (this == o) return true;
  2. Semak sama ada pautan ditakrifkan o, iaitu sama ada ia null.
    Jika pada masa hadapan, apabila membandingkan jenis objek, operator akan digunakan instanceof, item ini boleh dilangkau, kerana parameter ini kembali falsedalam kes ini null instanceof Object.
  3. Bandingkan jenis objek thismenggunakan ooperator instanceofatau kaedah getClass(), berpandukan penerangan di atas dan gerak hati anda sendiri.
  4. Jika kaedah equalsdiganti dalam subkelas, pastikan anda membuat panggilansuper.equals(o)
  5. Tukar jenis parameter okepada kelas yang diperlukan.
  6. Lakukan perbandingan semua medan objek penting:
    • untuk jenis primitif (kecuali floatdan double), menggunakan operator==
    • untuk medan rujukan anda perlu memanggil kaedah merekaequals
    • untuk tatasusunan, anda boleh menggunakan lelaran kitaran atau kaedahArrays.equals()
    • untuk jenis floatdan doubleperlu menggunakan kaedah perbandingan kelas pembalut yang sepadan Float.compare()danDouble.compare()
  7. Dan akhirnya, jawab tiga soalan: adakah kaedah yang dilaksanakan simetri ? Transitif ? Setuju ? Dua prinsip lain ( reflekstiviti dan kepastian ) biasanya dijalankan secara automatik.

Peraturan menimpa HashCode

Hash ialah nombor yang dijana daripada objek yang menerangkan keadaannya pada satu ketika. Nombor ini digunakan di Jawa terutamanya dalam jadual cincang seperti HashMap. Dalam kes ini, fungsi cincang untuk mendapatkan nombor berdasarkan objek mesti dilaksanakan dengan cara untuk memastikan pengagihan elemen yang agak sekata merentas jadual cincang. Dan juga untuk meminimumkan kemungkinan perlanggaran apabila fungsi mengembalikan nilai yang sama untuk kunci yang berbeza.

Kod cincang kontrak

Untuk melaksanakan fungsi cincang, spesifikasi bahasa mentakrifkan peraturan berikut:
  • memanggil kaedah hashCodelebih daripada sekali pada objek yang sama mesti mengembalikan nilai cincang yang sama, dengan syarat medan objek yang terlibat dalam pengiraan nilai tidak berubah.
  • memanggil kaedah hashCodepada dua objek harus sentiasa mengembalikan nombor yang sama jika objek adalah sama (memanggil kaedah equalspada objek ini mengembalikan true).
  • memanggil kaedah hashCodepada dua objek yang tidak sama mesti mengembalikan nilai cincang yang berbeza. Walaupun keperluan ini tidak wajib, perlu dipertimbangkan bahawa pelaksanaannya akan memberi kesan positif ke atas prestasi jadual cincang.

Kaedah equals dan hashCode mesti diatasi bersama

Berdasarkan kontrak yang diterangkan di atas, berikutan bahawa apabila mengatasi kaedah dalam kod anda equals, anda mesti sentiasa mengatasi kaedah tersebut hashCode. Oleh kerana sebenarnya dua contoh kelas adalah berbeza kerana ia berada dalam kawasan ingatan yang berbeza, ia perlu dibandingkan mengikut beberapa kriteria logik. Sehubungan itu, dua objek yang setara secara logik mesti mengembalikan nilai cincang yang sama. Apakah yang berlaku jika hanya satu daripada kaedah ini ditindih?
  1. equalsYa hashCodetidak

    Katakan kami mentakrifkan kaedah equalsdalam kelas kami dengan betul, dan hashCodememutuskan untuk meninggalkan kaedah seperti yang terdapat dalam kelas Object. Kemudian dari sudut pandangan kaedah equalskedua-dua objek akan sama secara logik, manakala dari sudut pandangan kaedah hashCodemereka tidak akan mempunyai persamaan. Oleh itu, dengan meletakkan objek dalam jadual cincang, kami menghadapi risiko tidak mendapatkannya kembali dengan kunci.
    Sebagai contoh, seperti ini:

    Map<Point, String> m = new HashMap<>();
    m.put(new Point(1, 1),Point A);
    // pointName == null
    String pointName = m.get(new Point(1, 1));

    Jelas sekali, objek yang diletakkan dan objek yang dicari adalah dua objek berbeza, walaupun secara logiknya sama. Tapi sebab mereka mempunyai nilai cincang yang berbeza kerana kami melanggar kontrak, kami boleh mengatakan bahawa kami kehilangan objek kami di suatu tempat di perut jadual cincang.

  2. hashCodeYa equalstidak.

    Apakah yang berlaku jika kita mengatasi kaedah hashCodedan equalsmewarisi pelaksanaan kaedah daripada kelas Object. Seperti yang anda ketahui, equalskaedah lalai hanya membandingkan penunjuk dengan objek, menentukan sama ada ia merujuk kepada objek yang sama. Mari kita anggap bahawa hashCodekita telah menulis kaedah mengikut semua kanun, iaitu, menghasilkannya menggunakan IDE, dan ia akan mengembalikan nilai hash yang sama untuk objek yang sama secara logik. Jelas sekali, dengan berbuat demikian kami telah mentakrifkan beberapa mekanisme untuk membandingkan dua objek.

    Oleh itu, contoh daripada perenggan sebelumnya harus secara teori dijalankan. Tetapi kami masih tidak akan dapat mencari objek kami dalam jadual cincang. Walaupun kita akan hampir dengan ini, kerana sekurang-kurangnya kita akan menemui bakul meja hash di mana objek itu akan terletak.

    Untuk berjaya mencari objek dalam jadual hash, selain membandingkan nilai hash kunci, penentuan kesamaan logik kunci dengan objek yang dicari juga digunakan. Iaitu, equalstiada cara untuk dilakukan tanpa mengatasi kaedah tersebut.

Algoritma umum untuk menentukan Kod hash

Di sini, nampaknya saya, anda tidak perlu terlalu risau dan menjana kaedah dalam IDE kegemaran anda. Kerana semua anjakan bit ini ke kanan dan kiri untuk mencari nisbah emas, iaitu taburan normal - ini adalah untuk lelaki yang degil sepenuhnya. Secara peribadi, saya ragu bahawa saya boleh melakukan lebih baik dan lebih pantas daripada Idea yang sama.

Daripada kesimpulan

Oleh itu, kita melihat bahawa kaedah equalsmemainkan hashCodeperanan yang jelas dalam bahasa Java dan direka untuk mendapatkan ciri kesamaan logik dua objek. Dalam kes kaedah, equalsini mempunyai hubungan langsung dengan membandingkan objek, dalam kes hashCodetidak langsung, apabila perlu, katakan, untuk menentukan lokasi anggaran objek dalam jadual cincang atau struktur data yang serupa untuk meningkatkan kelajuan mencari objek. Selain kontrak , equalsterdapat hashCodesatu lagi keperluan yang berkaitan dengan perbandingan objek. Ini ialah ketekalan kaedah compareToantara muka Comparabledengan equals. Keperluan ini mewajibkan pembangun untuk sentiasa kembali x.equals(y) == trueapabila x.compareTo(y) == 0. Iaitu, kita melihat bahawa perbandingan logik dua objek tidak boleh bercanggah di mana-mana dalam aplikasi dan harus sentiasa konsisten.

Sumber

Java Berkesan, Edisi Kedua. Joshua Bloch. Terjemahan percuma buku yang sangat bagus. Java, perpustakaan profesional. Jilid 1. Asas. Kay Horstmann. Kurang teori dan lebihkan amalan. Tetapi semuanya tidak dianalisis secara terperinci seperti Bloch. Walaupun terdapat pandangan pada equals(). Struktur data dalam gambar. HashMap Artikel yang sangat berguna pada peranti HashMap di Java. Daripada melihat sumber.
Komen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION