Mungkinkah sebuah instance sama dengan beberapa instance lain dari tipe yang lebih spesifik?

25

Saya telah membaca artikel ini: Cara Menulis Metode Kesetaraan di Jawa .

Pada dasarnya, ini memberikan solusi untuk metode equals () yang mendukung warisan:

Point2D twoD   = new Point2D(10, 20);
Point3D threeD = new Point3D(10, 20, 50);
twoD.equals(threeD); // true
threeD.equals(twoD); // true

Tapi apakah itu ide yang bagus? dua contoh ini tampaknya sama tetapi mungkin memiliki dua kode hash yang berbeda. Bukankah itu agak salah?

Saya percaya ini akan lebih baik dicapai dengan casting operan saja.

Wes
sumber
1
Contoh dengan titik berwarna seperti yang diberikan dalam tautan lebih masuk akal bagi saya. Saya akan mempertimbangkan bahwa titik 2D (x, y) dapat dilihat sebagai titik 3D dengan komponen nol Z (x, y, 0), dan saya ingin kesetaraan untuk mengembalikan false dalam kasus Anda. Bahkan, dalam artikel tersebut, ColoredPoint secara eksplisit dikatakan berbeda dari suatu Point dan selalu menghasilkan false.
coredump
10
Tidak ada yang lebih buruk dari tutorial yang melanggar konvensi umum ... Butuh bertahun-tahun untuk menghentikan kebiasaan semacam itu dari programmer.
corsiKa
3
@coredump Memperlakukan titik 2D sebagai memiliki zkoordinat nol mungkin merupakan konvensi yang berguna untuk beberapa aplikasi (sistem CAD awal yang menangani data lama datang ke pikiran). Tapi itu adalah konvensi yang sewenang-wenang. Pesawat dalam ruang dengan 3 dimensi atau lebih dapat memiliki orientasi sewenang-wenang ... itulah yang membuat masalah menarik menjadi menarik.
ben rudgers
2
Ini lebih dari sedikit salah .
Kevin Krumwiede

Jawaban:

71

Ini tidak boleh sama karena merusak transitivitas . Pertimbangkan dua ungkapan ini:

new Point3D(10, 20, 50).equals(new Point2D(10, 20)) // true
new Point2D(10, 20).equals(new Point3D(10, 20, 60)) // true

Karena persamaan adalah transitif, ini harus berarti bahwa ungkapan berikut ini juga benar:

new Point3D(10, 20, 50).equals(new Point3D(10, 20, 60))

Tapi tentu saja - tidak.

Jadi, ide casting Anda benar - berharap bahwa di Jawa, casting berarti casting jenis referensi. Yang Anda inginkan di sini adalah metode konversi yang akan membuat Point2Dobjek baru dari Point3Dobjek. Ini juga akan membuat ekspresi lebih bermakna:

twoD.equals(threeD.projectXY())
Idan Arye
sumber
1
Artikel ini menjelaskan implementasi yang memecah transitivitas dan menawarkan berbagai penyelesaian. Di domain tempat kami mengizinkan poin 2D, kami telah memutuskan bahwa dimensi ketiga tidak masalah. dan (10, 20, 50)sederajat (10, 20, 60)baik-baik saja. Kami hanya peduli 10dan 20.
ben rudgers
1
Haruskah Point2Dada projectXYZ()metode untuk memberikan Point3Drepresentasi itu sendiri? Dengan kata lain, haruskah implementasi saling kenal?
hjk
4
@ hjk Menyingkirkan Point2Dtampaknya lebih sederhana karena memproyeksikan poin 2D memerlukan mendefinisikan pesawat mereka dalam ruang 3D terlebih dahulu. Jika titik 2D tahu itu pesawat, maka itu sudah menjadi titik 3D. Jika tidak, itu tidak dapat diproyeksikan. Saya teringat akan Abbott's Flatland .
ben rudgers
@ Benrudgers Anda dapat, bagaimanapun, mendefinisikan Plane3Dobjek, yang akan mendefinisikan pesawat dalam ruang 3D, bahwa pesawat dapat memiliki liftmetode (2D-> 3D mengangkat, bukan memproyeksikan) yang akan menerima Point2Ddan nomor untuk "sumbu ketiga "- jarak dari pesawat di sepanjang pesawat normal. Untuk kemudahan penggunaan, Anda dapat mendefinisikan bidang umum sebagai konstanta statis, sehingga Anda dapat melakukan hal-hal sepertiPlane3D.XY.lift(new Point2D(10, 20), 50).equals(new Point3D(10, 20, 50))
Idan Arye
@IdanArye Saya mengomentari saran bahwa poin 2D harus memiliki metode proyeksi. Mengenai pesawat dengan metode pengangkatan, saya pikir itu akan membutuhkan dua argumen untuk masuk akal: titik 2D dan pesawat diasumsikan berada, yaitu benar-benar perlu menjadi proyeksi jika tidak memiliki titik ... dan jika memiliki intinya, mengapa tidak hanya memiliki titik 3D dan menghapus tipe data yang bermasalah dan aroma metode kludged? YMMV.
ben rudgers
10

Saya berjalan pergi dari membaca artikel berpikir tentang kebijaksanaan Alan J. Perlis:

Epigram 9. Lebih baik memiliki 100 fungsi beroperasi pada satu struktur data daripada 10 fungsi pada 10 struktur data.

Fakta bahwa mendapatkan "kesetaraan" dengan benar adalah jenis masalah yang membuat Martin Ordersky penemu Scala di malam hari harus memberi jeda tentang apakah mengesampingkan equalspohon warisan adalah ide yang bagus.

Apa yang terjadi ketika kita kurang beruntung untuk mendapatkannya ColoredPointadalah geometri kita gagal karena kita menggunakan pewarisan untuk memperbanyak tipe data daripada membuat yang bagus. Ini meskipun harus kembali dan memodifikasi simpul akar pohon warisan untuk membuat equalspekerjaan. Mengapa tidak hanya menambahkan zdan colorke Point?

Alasan yang baik akan melakukannya Pointdan ColoredPointberoperasi di domain yang berbeda ... setidaknya jika domain tersebut tidak pernah berbaur. Namun jika itu masalahnya, kita tidak perlu menimpanya equals. Membandingkan ColoredPointdan Pointuntuk kesetaraan hanya masuk akal di domain ketiga tempat mereka diizinkan untuk bergaul. Dan dalam hal itu, mungkin lebih baik untuk memiliki "kesetaraan" yang disesuaikan dengan domain ketiga daripada mencoba menerapkan semantik kesetaraan dari satu atau yang lain atau keduanya dari domain yang tidak tergulung. Dengan kata lain "kesetaraan" harus didefinisikan secara lokal ke tempat di mana kita memiliki lumpur yang mengalir dari kedua sisi karena kita mungkin tidak ingin ColoredPoint.equals(pt)gagal melawan contoh-contoh Pointbahkan jika penulis ColoredPointberpendapat itu adalah ide yang baik enam bulan lalu pada jam 2 pagi .

ben rudgers
sumber
6

Ketika dewa pemrograman lama menciptakan pemrograman berorientasi objek dengan kelas, mereka memutuskan ketika datang ke komposisi dan pewarisan untuk memiliki dua hubungan untuk objek: "adalah" dan "memiliki".
Ini sebagian memecahkan masalah subclass yang berbeda dari kelas induk tetapi membuatnya dapat digunakan tanpa melanggar kode. Karena turunan subclass "adalah" objek superclass dan dapat diganti secara langsung untuk itu, meskipun subclass memiliki lebih banyak fungsi anggota atau anggota data, "memiliki" menjamin bahwa ia akan melakukan semua fungsi induk dan memiliki semua anggota Jadi Anda bisa mengatakan Point3D "adalah" Point, dan Point2D "adalah" Point jika keduanya mewarisi dari Point. Selain itu Point3D bisa menjadi subclass dari Point2D.

Namun, kesetaraan antar kelas adalah masalah khusus domain, dan contoh di atas tidak jelas untuk apa yang dibutuhkan oleh programmer agar program dapat bekerja dengan benar. Secara umum, aturan domain-matematika diikuti dan nilai-nilai data akan menghasilkan kesetaraan jika Anda membatasi ruang lingkup perbandingan hanya dalam kasus ini dua dimensi, tetapi tidak jika Anda membandingkan semua anggota data.

Jadi Anda mendapatkan tabel persamaan penyempitan:

Both objects have same values, limited to subset of shared members

Child classes can be equal to parent classes if parent and childs
data members are the same.

Both objects entire data members are the same.

Objects must have all same values and be similar classes. 

Objects must have all same values and be the same class type. 

Equality is determined by specific logical conditions in the domain.

Only Objects that both point to same instance are equal. 

Anda biasanya memilih aturan yang paling ketat yang Anda bisa yang masih akan melakukan semua fungsi yang diperlukan dalam domain masalah Anda. Tes kesetaraan bawaan untuk angka dirancang sedemikian ketatnya untuk tujuan matematika, tetapi pemrogram memiliki banyak cara untuk mengatasinya jika itu bukan tujuannya, termasuk pembulatan ke atas / ke bawah, pemotongan, gt, lt, dll. . Objek dengan cap waktu sering dibandingkan dengan waktu pembuatannya sehingga setiap instance harus unik sehingga perbandingan menjadi sangat spesifik.

Faktor desain dalam hal ini adalah untuk menentukan cara yang efisien untuk membandingkan objek. Kadang-kadang perbandingan rekursif semua anggota objek data adalah apa yang harus Anda lakukan, dan itu bisa menjadi sangat mahal jika Anda memiliki banyak dan banyak objek dengan banyak anggota data. Alternatif adalah dengan hanya membandingkan nilai data yang relevan, atau memiliki objek menghasilkan nilai hash anggota data yang bersangkutan untuk perbandingan cepat dengan objek serupa lainnya, menjaga koleksi diurutkan dan dipangkas untuk membuat perbandingan lebih cepat dan kurang intensif cpu, dan mungkin memungkinkan objek yang identik dalam data yang akan diambil dan pointer duplikat ke objek tunggal diletakkan di tempatnya.

Chris Reid
sumber
2

Aturannya adalah, setiap kali Anda menimpa hashcode(), Anda menimpa equals(), dan sebaliknya. Apakah ini ide yang baik atau tidak tergantung pada penggunaan yang dimaksudkan. Secara pribadi, saya akan menggunakan metode yang berbeda ( isLike()atau serupa) untuk mencapai efek yang sama.

TMN
sumber
1
Bisa saja mengganti kode hash tanpa mengesampingkan sama dengan. Sebagai contoh, orang akan melakukan itu untuk menguji algoritma hashing yang berbeda untuk kondisi kesetaraan yang sama.
Patricia Shanahan
1

Seringkali berguna bagi kelas yang tidak menghadap ke publik untuk memiliki metode pengujian ekivalensi yang memungkinkan objek dari tipe yang berbeda untuk mempertimbangkan satu sama lain "sama" jika mereka mewakili informasi yang sama, tetapi karena Java tidak memungkinkan sarana yang kelasnya dapat meniru masing-masing lain itu sering baik untuk memiliki satu jenis pembungkus menghadap publik dalam kasus-kasus di mana dimungkinkan untuk memiliki objek yang setara dengan representasi yang berbeda.

Sebagai contoh, pertimbangkan kelas yang merangkum matriks doublenilai 2D yang tidak dapat diubah . Jika satu metode luar meminta matriks identitas ukuran 1000, yang kedua meminta matriks diagonal dan melewati array yang berisi 1000 yang, dan yang ketiga meminta matriks 2D dan melewati array 1000x1000 di mana elemen pada diagonal primer semuanya 1,0 dan semua yang lain adalah nol, objek yang diberikan kepada ketiga kelas dapat menggunakan backing store yang berbeda secara internal [yang pertama memiliki bidang tunggal untuk ukuran, yang kedua memiliki array ribuan elemen, dan yang ketiga memiliki array array 1000 elemen] tetapi harus melaporkan satu sama lain sebagai setara [karena ketiganya merangkum matriks tetap 1000x1000 dengan yang ada di diagonal dan nol di tempat lain].

Selain fakta bahwa ia menyembunyikan keberadaan jenis-jenis backing-store yang berbeda, pembungkus juga akan berguna untuk memfasilitasi perbandingan, karena memeriksa item untuk kesetaraan pada umumnya akan menjadi proses multi-langkah. Tanyakan item pertama apakah itu tahu apakah itu sama dengan item kedua; jika tidak tahu, tanyakan yang kedua apakah tahu apakah itu sama dengan yang pertama. Jika tidak ada objek yang tahu, maka tanyakan setiap array tentang konten elemen individualnya [orang mungkin menambahkan pemeriksaan lain sebelum memutuskan untuk melakukan rute perbandingan item-individu yang sangat lambat].

Perhatikan bahwa metode uji ekivalensi untuk setiap objek dalam skenario ini perlu mengembalikan nilai tiga-negara ("Ya, saya setara", "Tidak, saya tidak setara", atau "Saya tidak tahu"), jadi metode "sama dengan" yang normal tidak akan cocok. Sementara objek apa pun dapat dengan mudah menjawab "Saya tidak tahu" ketika ditanya tentang yang lain, menambahkan logika untuk misalnya matriks diagonal yang tidak akan repot bertanya matriks identitas atau matriks diagonal tentang elemen apa pun di luar diagonal utama akan sangat mempercepat perbandingan di antara objek tersebut. jenis.

supercat
sumber