Mengapa C # dan Java menggunakan persamaan referensi sebagai default untuk '=='?

32

Saya telah merenungkan mengapa Java dan C # (dan saya yakin bahasa lain) default untuk referensi kesetaraan ==.

Dalam pemrograman yang saya lakukan (yang tentunya hanya sebagian kecil dari masalah pemrograman), saya hampir selalu menginginkan kesetaraan logis ketika membandingkan objek daripada referensi kesetaraan. Saya mencoba memikirkan mengapa kedua bahasa ini menempuh rute ini alih-alih membalikkannya dan memiliki ==kesetaraan logis dan menggunakan .ReferenceEquals()persamaan referensi.

Jelas menggunakan kesetaraan referensi sangat sederhana untuk diterapkan dan memberikan perilaku yang sangat konsisten, tetapi sepertinya tidak cocok dengan sebagian besar praktik pemrograman yang saya lihat hari ini.

Saya tidak ingin terlihat acuh terhadap masalah dengan mencoba menerapkan perbandingan logis, dan itu harus diimplementasikan di setiap kelas. Saya juga menyadari bahwa bahasa-bahasa ini telah dirancang sejak lama, tetapi pertanyaan umumnya tetap ada.

Apakah ada beberapa manfaat utama dari gagal dalam hal ini yang hanya saya lewatkan, atau apakah masuk akal bahwa perilaku default harus kesetaraan logis, dan default kembali ke referensi kesetaraan itu kesetaraan logis tidak ada untuk kelas?

Ritsleting
sumber
3
Karena variabel adalah referensi? Karena variabel bertindak seperti pointer, masuk akal jika mereka dibandingkan seperti itu
Daniel Gratzer
C # menggunakan kesetaraan logis untuk tipe nilai seperti struct. Tapi apa yang seharusnya "kesetaraan logis standar" untuk dua objek dari jenis referensi yang berbeda? Atau untuk dua objek di mana satu adalah tipe A yang diwarisi dari B? Selalu "salah" seperti untuk struct? Bahkan ketika Anda memiliki objek yang sama direferensikan dua kali, pertama sebagai A, lalu sebagai B? Tidak masuk akal bagi saya.
Doc Brown
3
Dengan kata lain, apakah Anda bertanya mengapa dalam C #, jika Anda menimpanya Equals(), itu tidak secara otomatis mengubah perilaku ==?
svick

Jawaban:

29

C # melakukannya karena Java melakukannya. Java melakukannya karena Java tidak mendukung overloading operator. Karena kesetaraan nilai harus didefinisikan ulang untuk setiap kelas, itu tidak bisa menjadi operator, tetapi harus menjadi metode. IMO ini adalah keputusan yang buruk. Jauh lebih mudah untuk menulis dan membaca a == bdaripada a.equals(b), dan lebih alami untuk programmer dengan pengalaman C atau C ++, tetapi a == bhampir selalu salah. Bug dari penggunaan ==mana .equalsdiperlukan telah menyia-nyiakan ribuan orang dari jam programmer.

kevin cline
sumber
7
Saya pikir ada banyak pendukung kelebihan operator karena ada pencela, jadi saya tidak akan mengatakan "itu adalah keputusan yang buruk" sebagai pernyataan absolut. Contoh: dalam proyek C ++ saya bekerja di kami telah kelebihan beban ==untuk banyak kelas dan beberapa bulan yang lalu saya menemukan beberapa pengembang tidak tahu apa ==yang sebenarnya dilakukan. Selalu ada risiko ini ketika semantik beberapa konstruk tidak jelas. The equals()notasi memberitahu saya bahwa saya menggunakan metode kustom dan bahwa saya harus mencarinya di suatu tempat. Intinya: Saya pikir operator kelebihan adalah masalah terbuka secara umum.
Giorgio
9
Saya akan mengatakan Java tidak memiliki operator overload yang ditentukan pengguna . Banyak operator memiliki makna ganda (kelebihan beban) di Jawa. Lihatlah +misalnya, yang melakukan penambahan (nilai numerik) dan rangkaian string pada saat yang sama.
Joachim Sauer
14
Bagaimana bisa a == blebih alami bagi programmer dengan pengalaman C, karena C tidak mendukung overloading operator yang ditentukan pengguna? (Misalnya, cara C untuk membandingkan string adalah strcmp(a, b) == 0, tidak a == b.)
svick
Ini pada dasarnya adalah apa yang saya pikirkan, tetapi saya pikir saya akan meminta mereka yang memiliki lebih banyak pengalaman untuk memastikan saya tidak kehilangan sesuatu yang jelas.
Ritsleting
4
@svick: di C, tidak ada tipe string, maupun tipe referensi. Operasi string dilakukan melalui char *. Tampak jelas bagi saya bahwa membandingkan dua petunjuk untuk kesetaraan tidak sama dengan perbandingan string.
kevin cline
15

Jawaban singkatnya: Konsistensi

Untuk menjawab pertanyaan Anda dengan benar, saya sarankan kita mengambil langkah mundur dan melihat ke masalah apa arti kesetaraan dalam bahasa pemrograman. Setidaknya ada TIGA kemungkinan yang berbeda, yang digunakan dalam berbagai bahasa:

  • Referensi persamaan : berarti a = b benar jika a dan b merujuk ke objek yang sama. Tidak akan benar jika a dan b merujuk ke objek yang berbeda, bahkan jika semua atribut a dan b adalah sama.
  • Kesetaraan dangkal : berarti a = b benar jika semua atribut objek yang dirujuk oleh a dan b adalah identik. Kesetaraan dangkal dapat dengan mudah diimplementasikan dengan perbandingan bitwise dari ruang memori yang mewakili dua objek. Harap dicatat bahwa referensi kesetaraan menyiratkan kesetaraan dangkal
  • Kesetaraan yang dalam : berarti bahwa a = b benar jika setiap atribut dalam a dan b identik atau sangat sama. Harap perhatikan bahwa kesetaraan yang dalam diimplikasikan oleh persamaan referensi dan persamaan dangkal. Dalam pengertian ini, kesetaraan yang dalam adalah bentuk paling lemah dari kesetaraan dan referensi kesetaraan adalah yang terkuat.

Ketiga jenis kesetaraan ini sering digunakan karena mudah untuk diimplementasikan: ketiga pemeriksaan kesetaraan dapat dengan mudah dihasilkan oleh kompiler (dalam kasus kesetaraan yang mendalam, kompiler mungkin perlu menggunakan bit tag untuk mencegah loop tak terbatas jika struktur untuk dibandingkan memiliki referensi melingkar). Tetapi ada masalah lain: tidak ada yang cocok.

Dalam sistem non-sepele, kesetaraan objek sering didefinisikan sebagai sesuatu antara kesetaraan yang mendalam dan referensi. Untuk memeriksa apakah kita ingin menganggap dua objek sama dalam konteks tertentu, kita mungkin memerlukan beberapa atribut untuk dibandingkan dengan di mana ia berada dalam memori dan yang lainnya dengan kesetaraan yang mendalam, sementara beberapa atribut mungkin dibiarkan menjadi sesuatu yang berbeda sama sekali. Apa yang benar-benar kita inginkan adalah “jenis kesetaraan yang maju”, yang benar-benar menyenangkan, yang sering disebut dalam keseragaman semantik sastra . Semuanya sama jika sama, di domain kami. =)

Jadi kami dapat kembali ke pertanyaan Anda:

Apakah ada manfaat utama dari gagal dalam hal ini yang benar-benar saya lewatkan, atau apakah masuk akal bahwa perilaku default harus kesetaraan logis, dan default kembali ke referensi kesetaraan jika kesetaraan logis tidak ada untuk kelas?

Apa yang kita maksudkan ketika kita menulis 'a == b' dalam bahasa apa pun? Idealnya, harus selalu sama: kesetaraan semantik. Tapi itu tidak mungkin.

Salah satu pertimbangan utama adalah bahwa, setidaknya untuk tipe sederhana seperti angka, kami berharap bahwa dua variabel sama setelah penugasan dengan nilai yang sama. Lihat di bawah:

var a = 1;
var b = a;
if (a == b){
    ...
}
a = 3;
b = 3;
if (a == b) {
    ...
}

Dalam hal ini, kami berharap bahwa 'a sama dengan b' dalam kedua pernyataan. Yang lainnya akan menjadi gila. Sebagian besar (jika tidak semua) dari bahasa mengikuti konvensi ini. Oleh karena itu, dengan tipe sederhana (nilai alias) kita tahu cara mencapai kesetaraan semantik. Dengan benda, itu bisa menjadi sesuatu yang sangat berbeda. Lihat di bawah:

var a = new Something(1);
var b = a;
if (a == b){
    ...
}
b = new Something(1);
a.DoSomething();
b.DoSomething();
if (a == b) {
    ...
}

Kami berharap yang pertama 'jika' akan selalu benar. Tapi apa yang Anda harapkan pada 'jika' yang kedua? Itu sangat tergantung. Bisakah 'DoSomething' mengubah kesetaraan (semantik) a dan b?

Masalah dengan persamaan semantik adalah bahwa itu tidak dapat secara otomatis dihasilkan oleh kompiler untuk objek, juga tidak jelas dari tugas . Mekanisme harus disediakan bagi pengguna untuk mendefinisikan kesetaraan semantik. Dalam bahasa berorientasi objek, mekanisme itu adalah metode yang diwariskan: sama dengan . Membaca sepotong kode OO, kami tidak mengharapkan metode untuk memiliki implementasi yang persis sama di semua kelas. Kami terbiasa dengan warisan dan kelebihan.

Namun, dengan operator, kami mengharapkan perilaku yang sama. Ketika Anda melihat 'a == b' Anda harus mengharapkan jenis persamaan yang sama (dari 4 di atas) dalam semua situasi. Jadi, dengan tujuan untuk konsistensi , desainer bahasa menggunakan referensi kesetaraan untuk semua jenis. Seharusnya tidak tergantung pada apakah seorang programmer telah menimpa metode atau tidak.

PS: Bahasa Dee sedikit berbeda dari Java dan C #: operator sama berarti persamaan dangkal untuk tipe sederhana dan persamaan semantik untuk kelas yang ditentukan pengguna (dengan tanggung jawab untuk mengimplementasikan = operasi yang terletak pada pengguna - tidak ada standar yang disediakan). Karena, untuk tipe sederhana, persamaan dangkal selalu merupakan persamaan semantik, bahasanya konsisten. Namun, harga yang harus dibayar adalah bahwa operator yang sama secara default tidak ditentukan untuk tipe yang ditentukan pengguna. Anda harus mengimplementasikannya. Dan, terkadang, itu hanya membosankan.

HBA
sumber
2
When you see ‘a == b’ you should expect the same type of equality (from the 4 above) in all situations.Perancang bahasa Jawa menggunakan referensi kesetaraan untuk objek dan kesetaraan semantik untuk primitif. Tidak jelas bagi saya bahwa ini adalah keputusan yang tepat, atau bahwa keputusan ini lebih "konsisten" daripada membiarkan ==kelebihan muatan untuk kesamaan objek semantik.
Charles Salvia
Mereka menggunakan "persamaan kesetaraan referensi" untuk primitif juga. Saat Anda menggunakan "int i = 3" tidak ada petunjuk untuk nomornya, jadi Anda tidak dapat menggunakan referensi. Dengan string, "semacam" tipe primitif, itu lebih jelas: Anda harus menggunakan ".intern ()" atau penugasan langsung (String s = "abc") untuk menggunakan == (referensi persamaan).
Hbas
1
PS: C #, sebaliknya, tidak konsisten dengan senar itu. Dan IMHO, dalam hal ini, itu jauh lebih baik.
Hbas
@CharlesSalvia: Di Jawa, jika adan bmerupakan tipe yang sama, ekspresi a==bmenguji apakah adan bmenahan hal yang sama. Jika salah satu dari mereka memegang referensi ke objek # 291, dan yang lainnya memegang referensi ke objek # 572, mereka tidak memegang hal yang sama. The isi dari objek # 291 dan # 572 mungkin setara, tetapi variabel sendiri memegang hal yang berbeda.
supercat
2
@CharlesSalvia Ini dirancang sedemikian rupa sehingga Anda dapat melihat a == bdan tahu apa fungsinya. Demikian juga, Anda dapat melihat a.equals(b)dan menganggap itu kelebihan equals. Jika a == bpanggilan a.equals(b)(jika dilaksanakan), apakah itu membandingkan dengan referensi atau dengan konten? Tidak ingat Anda harus memeriksa kelas A. Kode ini tidak lagi cepat dibaca jika Anda bahkan tidak yakin apa yang dipanggil. Ini akan seolah-olah metode dengan tanda tangan yang sama diizinkan, dan metode yang dipanggil tergantung pada apa ruang lingkup saat ini. Program semacam itu tidak mungkin dibaca.
Neil
0

Saya mencoba memikirkan mengapa kedua bahasa ini menggunakan rute ini alih-alih membalikkannya dan menjadikan == menjadi kesetaraan logis dan menggunakan .ReferenceEquals () untuk referensi kesetaraan.

Karena pendekatan yang terakhir akan membingungkan. Mempertimbangkan:

if (null.ReferenceEquals(null)) System.out.println("ok");

Haruskah kode ini dicetak "ok", atau haruskah dilemparkan NullPointerException?

Atsby
sumber
-2

Untuk Java dan C # manfaatnya terletak pada mereka yang berorientasi objek.

Dari sudut pandang kinerja - kode yang lebih mudah untuk ditulis juga harus lebih cepat: karena OOP bermaksud untuk elemen yang berbeda secara logis untuk diwakili oleh objek yang berbeda, memeriksa kesetaraan referensi akan lebih cepat, dengan mempertimbangkan bahwa objek dapat menjadi cukup besar.

Dari sudut pandang logis - kesetaraan suatu objek dengan yang lain tidak harus sejelas membandingkan dengan properti objek untuk kesetaraan (mis. Bagaimana null == null ditafsirkan secara logis? Ini dapat berbeda dari kasus ke kasus).

Saya pikir apa yang menjadi intinya, adalah pengamatan Anda bahwa "Anda selalu menginginkan kesetaraan logis atas kesetaraan referensi". Konsensus di antara perancang bahasa mungkin sebaliknya. Saya pribadi merasa sulit untuk mengevaluasi ini, karena saya tidak memiliki spektrum pengalaman pemrograman yang luas. Secara kasar, saya lebih banyak menggunakan kesetaraan referensi dalam algoritme pengoptimalan, dan kesetaraan logis lebih banyak dalam menangani kumpulan data.

Rafael Emshoff
sumber
7
Referensi kesetaraan tidak ada hubungannya dengan orientasi objek. Justru sebaliknya, sebenarnya: salah satu sifat dasar dari orientasi objek adalah bahwa objek yang memiliki perilaku yang sama tidak dapat dibedakan. Satu objek harus dapat mensimulasikan objek lain. (Lagi pula, OO diciptakan untuk simulasi!) Referensi kesetaraan memungkinkan Anda untuk membedakan antara dua objek berbeda yang memiliki perilaku yang sama, memungkinkan Anda untuk membedakan antara objek yang disimulasikan dan yang asli. Oleh karena itu, Referensi Persamaan memecah orientasi objek. Program OO tidak boleh menggunakan Referensi Kesetaraan.
Jörg W Mittag
@ JörgWMittag: Untuk melakukan program berorientasi objek dengan benar mensyaratkan bahwa ada sarana untuk menanyakan objek X apakah kondisinya sama dengan Y [kondisi yang berpotensi transien], dan juga sarana menanyakan objek X apakah setara dengan Y [X setara dengan Y hanya jika kondisinya dijamin sama selamanya dengan Y]. Memiliki metode virtual yang terpisah untuk kesetaraan dan kesetaraan negara akan baik, tetapi untuk banyak jenis, ketidaksetaraan referensi akan menyiratkan non-kesetaraan, dan tidak ada alasan untuk menghabiskan waktu pada pengiriman metode virtual untuk membuktikannya.
supercat
-3

.equals()membandingkan variabel dengan isinya. alih-alih ==membandingkan objek dengan isinya ...

menggunakan objek lebih akurat untuk digunakan .equals()

Nuno Dias
sumber
3
Asumsi Anda salah. .equals () melakukan apa pun .equals () diberi kode untuk dilakukan. Biasanya dengan isi, tetapi tidak harus. Juga tidak lebih akurat untuk menggunakan .equals (). Itu tergantung pada apa yang ingin Anda capai.
Ritsleting