Mengapa javac memungkinkan beberapa pemeran yang tidak mungkin dan yang lainnya tidak?

52

Jika saya mencoba untuk melemparkan Stringke java.util.Date, kompiler Java menangkap kesalahan. Jadi mengapa kompiler tidak menandai yang berikut sebagai kesalahan?

List<String> strList = new ArrayList<>();                                                                      
Date d = (Date) strList;

Tentu saja, JVM melempar ClassCastExceptionpada saat runtime, tetapi kompiler tidak menandainya.

Perilaku ini sama dengan javac 1.8.0_212 dan 11.0.2.

Mike Woinoski
sumber
2
Tidak ada yang istimewa di Listsini. Date d = (Date) new Object();
Elliott Frisch
1
Saya telah bermain dengan Arduino belakangan ini. Saya suka kompiler yang tidak dengan senang hati menerima pemain dan kemudian melakukannya dengan hasil yang benar-benar tidak dapat diprediksi. String ke integer? Tentu saja! Double to integer? Ya pak! String ke boolean? Setidaknya yang satu kebanyakan menjadi salah ...
Stian Yttervik
@ElliottFrisch: Ada hubungan warisan yang jelas antara Date dan Object, tetapi tidak ada hubungan antara Date dan List. Jadi saya mengharapkan kompiler untuk menandai para pemain ini, cara yang sama akan menandai para pemain dari String ke Tanggal. Tapi seperti yang dijelaskan Zabuza dalam jawaban mereka yang sangat bagus, List adalah sebuah antarmuka, jadi para pemain akan sah jika strListitu adalah instance dari kelas yang mengimplementasikan List.
Mike Woinoski
Ini adalah pertanyaan yang sering muncul, dan saya yakin saya telah melihat banyak duplikatnya. Ini pada dasarnya adalah versi terbalik dari yang sangat terkait: stackoverflow.com/questions/21812289/…
Hulk
1
@StianYttervik -fpermissive adalah apa yang melakukan itu. Aktifkan peringatan kompiler.
bobsburner

Jawaban:

86

Para pemainnya adalah teknis memungkinkan. Tidak dapat dengan mudah dibuktikan oleh javac bahwa tidak demikian halnya dengan Anda dan JLS benar-benar mendefinisikan ini sebagai program Java yang valid, sehingga menandai kesalahan akan salah.

Ini karena Listantarmuka. Jadi Anda bisa memiliki subclass dari Dateyang benar-benar mengimplementasikan yang Listdisamarkan seperti di Listsini - dan kemudian casting itu Dateakan baik-baik saja. Sebagai contoh:

public class SneakyListDate extends Date implements List<Foo> {
    ...
}

Lalu:

List<Foo> list = new SneakyListDate();
Date date = (Date) list; // This one is valid, compiles and runs just fine

Mendeteksi skenario semacam itu mungkin tidak selalu memungkinkan, karena akan memerlukan informasi runtime jika instance berasal dari, misalnya, suatu metode. Dan bahkan jika, itu akan membutuhkan lebih banyak upaya untuk kompiler. Kompilator hanya mencegah gips yang benar-benar tidak mungkin karena tidak ada cara bagi pohon kelas untuk mencocokkan sama sekali. Yang tidak terjadi di sini, seperti yang terlihat.

Perhatikan bahwa JLS mengharuskan kode Anda menjadi program Java yang valid. Dalam 5.1.6.1. Konversi Referensi Sempit yang Diperbolehkan dikatakan:

Konversi penyempitan referensi ada dari tipe Sreferensi ke tipe referensi Tjika semua hal berikut ini benar :

  • [...]
  • Salah satu dari kasus berikut ini berlaku :
    • [...]
    • Sadalah tipe antarmuka, Tadalah tipe kelas, dan Ttidak menyebutkan nama finalkelas.

Jadi, bahkan jika kompiler dapat mengetahui bahwa case Anda sebenarnya tidak mungkin terbukti, ia tidak diperbolehkan menandai kesalahan karena JLS mendefinisikannya sebagai program Java yang valid.

Itu hanya akan diizinkan untuk menunjukkan peringatan.

Zabuzard
sumber
16
Dan perlu diperhatikan, bahwa alasannya menangkap kasus dengan String, adalah bahwa String adalah final, sehingga kompiler tahu bahwa tidak ada kelas yang dapat memperpanjangnya.
MTilsted
5
Sebenarnya, saya tidak berpikir itu adalah "finalitas" dari String yang membuat myDate = (Date) myStringgagal. Menggunakan terminologi JLS, pernyataan berusaha untuk mengkonversi dari S(the String) ke T(the Date). Di sini, Sbukan tipe antarmuka, jadi kondisi JLS yang dikutip di atas tidak berlaku. Sebagai contoh, coba casting Kalender ke Tanggal dan Anda akan mendapatkan kesalahan kompilator meskipun kelas tidak final.
Mike Woinoski
1
Saya tidak tahu apakah akan kecewa atau tidak, kompiler tidak dapat melakukan analisis statis yang cukup untuk membuktikan bahwa strList hanya dapat bertipe ArrayList.
Joshua
3
Kompiler tidak dilarang memeriksa. Tetapi dilarang menyebutnya kesalahan. Itu akan membuat kompiler tidak sesuai. (Lihat jawaban saya ...)
Stephen C
3
Untuk menambahkan sedikit istilah, kompiler perlu membuktikan bahwa jenisnya tidakDate & List dapat dihuni , itu tidak cukup untuk membuktikan bahwa itu tidak berpenghuni saat ini (mungkin di masa depan).
Polygnome
15

Biarkan kami mempertimbangkan generalisasi contoh Anda:

List<String> strList = someMethod();       
Date d = (Date) strList;

Ini adalah alasan utama mengapa Date d = (Date) strList;bukan kesalahan kompilasi.

  • The Alasan intuitif adalah bahwa compiler tidak (pada umumnya) mengetahui jenis yang tepat dari objek dikembalikan oleh metode panggilan. Ada kemungkinan bahwa selain menjadi kelas yang mengimplementasikan List, itu juga merupakan subkelas dari Date.

  • The alasan teknis adalah bahwa Java Language Specification "memungkinkan" yang konversi referensi penyempitan yang sesuai dengan jenis cor ini. Menurut JLS 5.1.6.1 :

    "Konversi penyempitan referensi ada dari tipe Sreferensi ke tipe referensi Tjika semua hal berikut ini benar:"

    ...

    5) " Sadalah tipe antarmuka, Tadalah tipe kelas, dan Ttidak memberi nama finalkelas."

    ...

    Di tempat yang berbeda, JLS juga mengatakan bahwa pengecualian dapat dilempar saat runtime ...

    Perhatikan bahwa penentuan JLS 5.1.6.1 hanya didasarkan pada tipe variabel yang dideklarasikan yang terlibat daripada tipe runtime yang sebenarnya. Dalam kasus umum, kompiler tidak dan tidak bisa mengetahui tipe runtime yang sebenarnya.


Jadi, mengapa kompiler Java tidak dapat bekerja bahwa para pemain tidak akan bekerja?

  • Dalam contoh saya, someMethodpanggilan dapat mengembalikan objek dengan berbagai jenis. Bahkan jika kompiler dapat menganalisis tubuh metode dan menentukan set tipe yang tepat yang dapat dikembalikan, tidak ada yang menghentikan seseorang mengubahnya untuk mengembalikan tipe yang berbeda ... setelah mengkompilasi kode yang memanggilnya. Ini adalah alasan dasar mengapa JLS 5.1.6.1 mengatakan apa yang dikatakannya.

  • Dalam contoh Anda, kompiler pintar bisa mengetahui bahwa para pemain tidak pernah bisa berhasil. Dan diizinkan memancarkan peringatan waktu kompilasi untuk menunjukkan masalahnya.

Jadi mengapa kompiler pintar tidak diizinkan untuk mengatakan ini adalah kesalahan?

  • Karena JLS mengatakan bahwa ini adalah program yang valid. Titik. Setiap kompiler yang menyebut ini sebagai kesalahan tidak akan memenuhi persyaratan Java.

  • Juga, setiap kompiler yang menolak program Java yang JLS dan kompiler lain katakan valid, merupakan penghalang untuk portabilitas kode sumber Java.

Stephen C
sumber
4
Terpilih untuk fakta bahwa setelah mengkompilasi kelas pemanggil implementasi fungsi yang dipanggil dapat berubah , jadi bahkan jika itu dapat dibuktikan pada waktu kompilasi, dengan implementasi callee saat ini, bahwa para pemain tidak mungkin, ini mungkin tidak akan terjadi pada waktu dijalankan nanti ketika callee telah berubah atau diganti.
Peter - Pasang kembali Monica
2
Suara positif untuk menyoroti masalah portabilitas yang akan diperkenalkan jika kompiler mencoba menjadi terlalu pintar.
Mike Woinoski
2

5.5.1. Referensi Tipe Pengecoran:

Diberikan jenis referensi waktu kompilasi S(sumber) dan tipe referensi waktu kompilasi T(target), konversi casting ada dari Shingga T jika tidak ada kesalahan waktu kompilasi terjadi karena aturan berikut.

[...]

Jika Smerupakan jenis antarmuka:

  • [...]

  • Jika Tadalah kelas atau jenis antarmuka yang belum final, maka jika ada supertype Xdari T, dan supertype Ydari S, seperti bahwa kedua Xdan Ybeberapa jenis parameter provably yang berbeda, dan bahwa erasures dari Xdan Yadalah sama, kesalahan saat kompilasi terjadi.

    Kalau tidak, para pemain selalu sah pada waktu kompilasi (karena meskipun Ttidak menerapkan S, subkelas Tkekuatan).

List<String> adalah S dan Dateadalah Tdalam kasus Anda.

Oleksandr Pyrohov
sumber