Perbedaan antara C # dan operator terner Java (? :)

94

Saya seorang pemula C # dan saya baru saja mengalami masalah. Ada perbedaan antara C # dan Java saat berhadapan dengan operator terner ( ? :).

Di segmen kode berikut, mengapa baris ke-4 tidak berfungsi? Kompilator menampilkan pesan kesalahan there is no implicit conversion between 'int' and 'string'. Baris ke-5 juga tidak berfungsi. Keduanya Listadalah objek, bukan?

int two = 2;
double six = 6.0;
Write(two > six ? two : six); //param: double
Write(two > six ? two : "6"); //param: not object
Write(two > six ? new List<int>() : new List<string>()); //param: not object

Namun, kode yang sama berfungsi di Java:

int two = 2;
double six = 6.0;
System.out.println(two > six ? two : six); //param: double
System.out.println(two > six ? two : "6"); //param: Object
System.out.println(two > six ? new ArrayList<Integer>()
                   : new ArrayList<String>()); //param: Object

Fitur bahasa apa di C # yang hilang? Jika ada, mengapa tidak ditambahkan?

blackr1234
sumber
4
Sesuai dengan msdn.microsoft.com/en-us/library/ty67wk28.aspx "Jenis ekspresi_pertama dan ekspresi_kedua harus sama, atau konversi implisit harus ada dari satu jenis ke jenis lainnya."
Egor
2
Saya sudah lama tidak melakukan C #, tetapi bukankah pesan yang Anda kutip mengatakannya? Dua cabang terner harus mengembalikan tipe data yang sama. Anda memberinya int dan String. C # tidak tahu cara mengonversi int menjadi String secara otomatis. Anda perlu meletakkan jenis konversi eksplisit di atasnya.
Jay
2
@ blackr1234 Karena an intbukan objek. Itu primitif.
Kode-Magang
3
@ Code-Apprentice ya mereka memang sangat berbeda - C # memiliki reifikasi runtime, jadi daftarnya adalah tipe yang tidak berhubungan. Oh, dan Anda juga bisa memasukkan kovarian / kontravarian antarmuka generik ke dalam campuran jika Anda mau, untuk memperkenalkan beberapa tingkat hubungan;)
Lucas Trzesniewski
3
Untuk keuntungan OP: penghapusan tipe di Java berarti ArrayList<String>dan ArrayList<Integer>menjadi hanya ArrayListdi bytecode. Ini berarti bahwa mereka tampak memiliki tipe yang sama persis pada waktu proses. Ternyata di C # mereka berbeda tipe.
Code-Apprentice

Jawaban:

106

Melihat melalui C # 5 Spesifikasi Bahasa bagian 7.14: Operator Bersyarat kita dapat melihat yang berikut:

  • Jika x memiliki tipe X dan y memiliki tipe Y maka

    • Jika konversi implisit (§6.1) ada dari X ke Y, tetapi tidak dari Y ke X, maka Y adalah jenis ekspresi kondisional.

    • Jika konversi implisit (§6.1) ada dari Y ke X, tetapi tidak dari X ke Y, maka X adalah jenis ekspresi kondisional.

    • Jika tidak, tidak ada tipe ekspresi yang dapat ditentukan, dan kesalahan waktu kompilasi terjadi

Dengan kata lain: ia mencoba untuk menemukan apakah x dan y dapat dikonversi ke eachother dan jika tidak, kesalahan kompilasi terjadi. Dalam kasus kami intdan stringtidak memiliki konversi eksplisit atau implisit sehingga tidak dapat dikompilasi.

Bandingkan ini dengan Spesifikasi Bahasa Java 7 bagian 15.25: Operator Bersyarat :

  • Jika operan kedua dan ketiga memiliki tipe yang sama (yang mungkin merupakan tipe null), maka itu adalah tipe ekspresi kondisional. ( TIDAK )
  • Jika salah satu dari operan kedua dan ketiga adalah tipe primitif T, dan tipe yang lain adalah hasil dari penerapan konversi tinju (§5.1.7) ke T, maka tipe ekspresi kondisionalnya adalah T. ( NO )
  • Jika salah satu dari operan kedua dan ketiga berjenis null dan jenis yang lain adalah jenis referensi, maka jenis ekspresi kondisional adalah jenis referensi tersebut. ( TIDAK )
  • Sebaliknya, jika operan kedua dan ketiga memiliki tipe yang dapat diubah (§5.1.8) menjadi tipe numerik, maka terdapat beberapa kasus: ( NO )
  • Jika tidak, operan kedua dan ketiga masing-masing memiliki tipe S1 dan S2. Misalkan T1 adalah tipe yang dihasilkan dari penerapan konversi tinju ke S1, dan biarkan T2 menjadi tipe yang dihasilkan dari penerapan konversi tinju ke S2.
    Jenis ekspresi kondisional adalah hasil dari penerapan konversi tangkapan (§5.1.10) ke lub (T1, T2) (§15.12.2.7). ( YA )

Dan, lihat bagian 15.12.2.7. Argumen Tipe Inferensi Berdasarkan Argumen Aktual, kita dapat melihat mencoba untuk menemukan nenek moyang yang sama yang akan berfungsi sebagai tipe yang digunakan untuk panggilan yang mendarat dengannya Object. Object adalah argumen yang dapat diterima sehingga panggilan tersebut akan berhasil.

Jeroen Vannevel
sumber
1
Saya membaca spesifikasi C # yang mengatakan harus ada konversi implisit setidaknya dalam satu arah. Kesalahan penyusun terjadi hanya jika tidak ada konversi implisit di kedua arah.
Kode-Magang
1
Tentu ini masih menjadi jawaban terlengkap karena Anda mengutip langsung dari spesifikasinya.
Code-Apprentice
Ini sebenarnya hanya diubah dengan Java 5 (saya pikir, mungkin 6). Sebelumnya Java memiliki perilaku yang persis sama dengan C #. Karena cara enum diimplementasikan, ini mungkin menyebabkan lebih banyak kejutan di Java daripada C # meskipun itu merupakan perubahan yang bagus selama ini.
Voo
@Voo: FYI, Java 5 dan Java 6 memiliki versi spesifikasi yang sama ( Spesifikasi Bahasa Java , Edisi Ketiga), jadi pasti tidak berubah di Java 6.
ruakh
86

Jawaban yang diberikan bagus; Saya akan menambahkan kepada mereka bahwa aturan C # ini adalah konsekuensi dari pedoman desain yang lebih umum. Ketika diminta untuk menyimpulkan jenis ekspresi dari salah satu dari beberapa pilihan, C # memilih yang terbaik dan unik dari mereka . Artinya, jika Anda memberi C # beberapa pilihan seperti "Jerapah, Mamalia, Hewan", maka C # dapat memilih yang paling umum - Hewan - atau mungkin memilih yang paling spesifik - Jerapah - bergantung pada situasinya. Tapi itu harus memilih salah satu pilihan yang diberikan . C # tidak pernah mengatakan "pilihan saya adalah antara Kucing dan Anjing, oleh karena itu saya akan menyimpulkan bahwa Hewan adalah pilihan terbaik". Itu bukan pilihan yang diberikan, jadi C # tidak bisa memilihnya.

Dalam kasus operator terner C # mencoba untuk memilih tipe yang lebih umum dari int dan string, tetapi tidak ada tipe yang lebih umum. Daripada memilih tipe yang bukan merupakan pilihan di tempat pertama, seperti objek, C # memutuskan bahwa tidak ada tipe yang dapat disimpulkan.

Saya perhatikan juga bahwa ini sesuai dengan prinsip desain C # lainnya: jika ada yang salah, beri tahu pengembang. Bahasa tidak mengatakan "Saya akan menebak apa yang Anda maksud dan mengacaukannya jika saya bisa". Bahasa mengatakan "Saya pikir Anda telah menulis sesuatu yang membingungkan di sini, dan saya akan memberitahu Anda tentang itu."

Juga, saya perhatikan bahwa C # tidak beralasan dari variabel ke nilai yang diberikan , melainkan ke arah lain. C # tidak mengatakan "Anda menugaskan ke variabel objek oleh karena itu ekspresi harus dapat diubah menjadi objek, oleh karena itu saya akan memastikan bahwa itu". Sebaliknya, C # mengatakan "ekspresi ini harus memiliki tipe, dan saya harus dapat menyimpulkan bahwa tipe tersebut kompatibel dengan objek". Karena ekspresi tidak memiliki tipe, kesalahan dihasilkan.

Eric Lippert
sumber
6
Saya melewati paragraf pertama bertanya-tanya mengapa tiba-tiba muncul kovariansi / ketertarikan, lalu saya mulai lebih setuju pada paragraf kedua, lalu saya melihat siapa yang menulis jawabannya ... Seharusnya lebih cepat menebak. Pernahkah Anda memperhatikan seberapa sering Anda menggunakan 'Jerapah' sebagai contoh ? Anda pasti sangat menyukai Jerapah.
Pharap
21
Jerapah itu luar biasa!
Eric Lippert
9
@Pharap: Dengan sangat serius, ada alasan pedagogis mengapa saya sering menggunakan jerapah. Pertama-tama, jerapah terkenal di seluruh dunia. Kedua, itu semacam kata yang menyenangkan untuk diucapkan, dan mereka terlihat sangat aneh sehingga menjadi gambar yang mudah diingat. Ketiga, menggunakan, katakanlah, "jerapah" dan "kura-kura" dalam analogi yang sama sangat menekankan "kedua kelas ini, meskipun mereka mungkin berbagi kelas dasar, adalah hewan yang sangat berbeda dengan karakteristik yang sangat berbeda". Pertanyaan tentang sistem tipe sering kali memiliki contoh dimana tipe secara logis sangat mirip, yang mendorong intuisi Anda ke arah yang salah.
Eric Lippert
7
@Pharap: Artinya, pertanyaannya biasanya seperti "mengapa daftar pabrik penyedia faktur pelanggan tidak dapat digunakan sebagai daftar pabrik penyedia faktur?" dan intuisi Anda mengatakan "ya, pada dasarnya itu adalah hal yang sama", tetapi ketika Anda mengatakan "mengapa ruangan yang penuh dengan jerapah tidak dapat digunakan sebagai ruangan yang penuh dengan mamalia?" kemudian menjadi analogi yang lebih intuitif untuk mengatakan "karena ruangan yang penuh dengan mamalia dapat berisi harimau, ruangan yang penuh dengan jerapah tidak".
Eric Lippert
6
@ Eric Saya awalnya mengalami masalah ini dengan int? b = (a != 0 ? a : (int?) null). Ada juga pertanyaan ini , bersama dengan semua pertanyaan terkait di sampingnya. Jika Anda terus mengikuti tautan, ada cukup banyak. Sebagai perbandingan, saya belum pernah mendengar seseorang mengalami masalah dunia nyata dengan cara Java melakukannya.
BlueRaja - Danny Pflughoeft
24

Mengenai bagian generik:

two > six ? new List<int>() : new List<string>()

Dalam C #, kompilator mencoba untuk mengubah bagian ekspresi kanan menjadi beberapa tipe umum; karena List<int>dan List<string>merupakan dua tipe konstruksi yang berbeda, yang satu tidak dapat dikonversi ke yang lain.

Di Java, kompilator mencoba menemukan supertipe umum daripada mengonversinya, jadi kompilasi kode melibatkan penggunaan implisit wildcard dan penghapusan tipe ;

two > six ? new ArrayList<Integer>() : new ArrayList<String>()

memiliki tipe kompilasi ArrayList<?>(sebenarnya, bisa juga ArrayList<? extends Serializable>atau ArrayList<? extends Comparable<?>>, bergantung pada konteks penggunaan, karena keduanya adalah supertipe generik umum) dan tipe waktu proses mentah ArrayList(karena ini adalah supertipe raw umum).

Misalnya (uji sendiri) ,

void test( List<?> list ) {
    System.out.println("foo");
}

void test( ArrayList<Integer> list ) { // note: can't use List<Integer> here
                                 // since both test() methods would clash after the erasure
    System.out.println("bar");
}

void test() {
    test( true ? new ArrayList<Object>() : new ArrayList<Object>() ); // foo
    test( true ? new ArrayList<Integer>() : new ArrayList<Object>() ); // foo 
    test( true ? new ArrayList<Integer>() : new ArrayList<Integer>() ); // bar
} // compiler automagically binds the correct generic QED
Mathieu Guindon
sumber
Sebenarnya Write(two > six ? new List<object>() : new List<string>());tidak berhasil juga.
blackr1234
2
@ blackr1234 persis: Saya tidak ingin mengulangi apa yang sudah dikatakan jawaban lain, tetapi ekspresi terner perlu mengevaluasi ke suatu tipe, dan penyusun tidak akan berusaha menemukan "penyebut persekutuan terendah" dari keduanya.
Mathieu Guindon
2
Sebenarnya ini bukan tentang penghapusan tipe, tapi tentang tipe wildcard. Penghapusan ArrayList<String>dan ArrayList<Integer>akan menjadi ArrayList(tipe mentah), tetapi tipe yang disimpulkan untuk operator terner ini adalah ArrayList<?>(tipe wildcard). Secara lebih umum, runtime Java yang mengimplementasikan generik melalui penghapusan tipe tidak berpengaruh pada tipe waktu kompilasi ekspresi.
meriton
1
@vaxquis terima kasih! Saya seorang C #, Java generics membingungkan saya ;-)
Mathieu Guindon
2
Sebenarnya, tipe two > six ? new ArrayList<Integer>() : new ArrayList<String>()is ArrayList<? extends Serializable&Comparable<?>>yang berarti Anda dapat menugaskannya ke variabel tipe ArrayList<? extends Serializable>dan juga variabel tipe ArrayList<? extends Comparable<?>>, tapi tentu saja ArrayList<?>, yang setara dengan ArrayList<? extends Object>, berfungsi juga.
Holger
6

Baik di Java dan C # (dan sebagian besar bahasa lainnya), hasil ekspresi memiliki tipe. Dalam kasus operator terner, ada dua kemungkinan subekspresi yang dievaluasi untuk hasil dan keduanya harus memiliki jenis yang sama. Dalam kasus Java, intvariabel dapat diubah menjadi Integerdengan autoboxing. Sekarang karena keduanya Integerdan Stringmewarisi dari Object, keduanya dapat dikonversi ke jenis yang sama dengan konversi penyempitan sederhana.

Di sisi lain, di C #, an intadalah primitif dan tidak ada konversi implisit ke stringatau yang lainnya object.

Kode-Magang
sumber
Terima kasih atas jawabannya. Autoboxing adalah apa yang saya pikirkan saat ini. Apakah C # tidak mengizinkan autoboxing?
blackr1234
2
Saya tidak berpikir ini benar karena meninju sebuah int ke suatu objek sangat mungkin dilakukan di C #. Perbedaannya di sini adalah, saya percaya, bahwa C # hanya mengambil pendekatan yang sangat santai dalam menentukan apakah itu diperbolehkan atau tidak.
Jeroen Vannevel
9
Ini tidak ada hubungannya dengan tinju otomatis, yang akan terjadi sama seperti di C #. Perbedaannya adalah bahwa C # tidak mencoba mencari supertipe umum sementara Java (karena hanya Java 5!). Anda dapat dengan mudah mengujinya dengan mencoba melihat apa yang terjadi jika Anda mencobanya dengan 2 kelas khusus (yang jelas-jelas mewarisi keduanya dari objek). Juga ada konversi implisit dari intmenjadi object.
Voo
3
Dan untuk lebih memilih sedikit lagi: Konversi dari Integer/Stringke Objectbukanlah konversi yang menyempit, justru sebaliknya :-)
Voo
1
Integerdan Stringjuga menerapkan Serializabledan Comparablejadi tugas untuk salah satu dari mereka akan bekerja dengan baik, misalnya Comparable<?> c=condition? 6: "6";atau List<? extends Serializable> l = condition? new ArrayList<Integer>(): new ArrayList<String>();yang kode Java hukum.
Holger
5

Ini sangat mudah. Tidak ada konversi implisit antara string dan int. operator terner membutuhkan dua operan terakhir untuk memiliki tipe yang sama.

Mencoba:

Write(two > six ? two.ToString() : "6");
ChiralMichael
sumber
11
Pertanyaannya adalah apa yang menyebabkan perbedaan antara Java dan C #. Ini tidak menjawab pertanyaan itu.
Jeroen Vannevel