Apa konsep penghapusan dalam obat generik di Jawa?
Ini pada dasarnya cara generik diimplementasikan di Jawa melalui tipu daya kompiler. Kode generik yang dikompilasi sebenarnya hanya menggunakan di java.lang.Object
mana pun Anda berbicara T
(atau parameter tipe lainnya) - dan ada beberapa metadata untuk memberi tahu kompiler bahwa itu benar-benar tipe generik.
Ketika Anda mengkompilasi beberapa kode terhadap tipe atau metode umum, kompiler bekerja apa yang Anda maksud sebenarnya (yaitu apa argumen tipe T
adalah) dan memverifikasi pada waktu kompilasi bahwa Anda melakukan hal yang benar, tetapi kode yang dipancarkan lagi hanya berbicara dalam hal java.lang.Object
- kompiler menghasilkan gips tambahan jika diperlukan. Pada waktu eksekusi, a List<String>
dan a List<Date>
persis sama; informasi tipe tambahan telah dihapus oleh kompiler.
Bandingkan ini dengan, katakanlah, C #, di mana informasi disimpan pada waktu eksekusi, yang memungkinkan kode berisi ekspresi seperti typeof(T)
yang setara dengan T.class
- kecuali bahwa yang terakhir tidak valid. (Ada perbedaan lebih lanjut antara .NET generics dan Java generics, ingatkan Anda.) Tipe erasure adalah sumber dari banyak pesan peringatan / kesalahan "ganjil" ketika berhadapan dengan generik Java.
Sumber lain:
Object
(dalam skenario yang diketik dengan lemah) sebenarnya aList<String>
) misalnya. Di Jawa itu tidak layak - Anda dapat mengetahui bahwa itu adalahArrayList
, tetapi tidak seperti apa tipe generik aslinya. Hal semacam ini dapat muncul dalam situasi serialisasi / deserialisasi, misalnya. Contoh lain adalah ketika sebuah wadah harus mampu membuat instance dari tipe generiknya - Anda harus meneruskan tipe itu secara terpisah di Java (asClass<T>
).Class<T>
parameter ke konstruktor (atau metode umum) hanya karena Java tidak menyimpan informasi itu. LihatEnumSet.allOf
misalnya - argumen tipe umum untuk metode harus cukup; mengapa saya perlu menentukan argumen "normal" juga? Jawab: ketik erasure. Hal semacam ini mencemari API. Karena ketertarikan, apakah Anda sudah sering menggunakan .NET generics? (lanjutan)Sama seperti catatan tambahan, ini adalah latihan yang menarik untuk benar-benar melihat apa yang dilakukan kompiler ketika melakukan penghapusan - membuat keseluruhan konsep sedikit lebih mudah untuk dipahami. Ada flag khusus yang dapat Anda operasikan kompilator untuk menghasilkan file java yang generiknya terhapus dan gips dimasukkan. Sebuah contoh:
Ini
-printflat
adalah bendera yang diserahkan ke kompiler yang menghasilkan file. (Bagian-XD
inilah yang memberitahujavac
untuk menyerahkannya ke toples yang dapat dieksekusi yang sebenarnya melakukan kompilasi daripada hanyajavac
, tapi saya ngelantur ...) Hal-d output_dir
ini diperlukan karena kompiler memerlukan tempat untuk meletakkan file .java baru.Ini, tentu saja, tidak lebih dari sekadar penghapusan; semua hal otomatis yang dilakukan kompiler dilakukan di sini. Sebagai contoh, konstruktor default juga dimasukkan,
for
loop gaya foreach baru diperluas kefor
loop biasa , dll. Menyenangkan untuk melihat hal-hal kecil yang terjadi secara otomatis.sumber
Penghapusan, secara harfiah berarti bahwa informasi jenis yang ada dalam kode sumber dihapus dari bytecode yang dikompilasi. Mari kita pahami ini dengan beberapa kode.
Jika Anda mengkompilasi kode ini dan kemudian mendekompilasi dengan Java decompiler, Anda akan mendapatkan sesuatu seperti ini. Perhatikan bahwa kode yang didekompilasi tidak mengandung jejak dari informasi jenis yang ada dalam kode sumber asli.
sumber
jigawot
mengatakan, itu berhasil.Untuk menyelesaikan jawaban Jon Skeet yang sudah sangat lengkap, Anda harus menyadari konsep tipe erasure berasal dari kebutuhan kompatibilitas dengan Java versi sebelumnya .
Awalnya disajikan di EclipseCon 2007 (tidak lagi tersedia), kompatibilitasnya termasuk poin-poin tersebut:
Jawaban asli:
Karenanya:
Ada proposisi untuk reifikasi yang lebih besar . Tentukan kembali bahwa "Anggap konsep abstrak sebagai nyata", di mana konstruksi bahasa harus menjadi konsep, bukan hanya gula sintaksis.
Saya juga harus menyebutkan
checkCollection
metode Java 6, yang mengembalikan tampilan dinamis yang aman untuk koleksi yang ditentukan. Upaya apa pun untuk memasukkan elemen dari tipe yang salah akan menghasilkan langsungClassCastException
.Mekanisme generik dalam bahasa menyediakan pengecekan tipe waktu kompilasi (statis), tetapi dimungkinkan untuk mengalahkan mekanisme ini dengan gips yang tidak dicentang .
Biasanya ini bukan masalah, karena kompilator mengeluarkan peringatan pada semua operasi yang tidak dicentang tersebut.
Namun, ada saat-saat pengecekan tipe statis saja tidak cukup, seperti:
ClassCastException
, yang menunjukkan bahwa elemen yang diketik salah dimasukkan ke dalam koleksi parameter. Sayangnya, pengecualian dapat terjadi kapan saja setelah elemen yang salah dimasukkan, sehingga biasanya memberikan sedikit atau tidak ada informasi mengenai sumber masalah yang sebenarnya.Pembaruan Juli 2012, hampir empat tahun kemudian:
Sekarang (2012) dirinci dalam " Aturan Kompatibilitas Migrasi API (Tes Tanda Tangan) "
sumber
Melengkapi jawaban Jon Skeet yang sudah dilengkapi ...
Telah disebutkan bahwa menerapkan obat generik melalui penghapusan menyebabkan beberapa batasan yang mengganggu (misalnya tidak
new T[42]
). Juga telah disebutkan bahwa alasan utama untuk melakukan hal-hal dengan cara ini adalah kompatibilitas mundur dalam bytecode. Ini juga (kebanyakan) benar. Bytecode yang dihasilkan -target 1.5 agak berbeda dari hanya de-sugared casting -target 1.4. Secara teknis, itu bahkan mungkin (melalui tipuan besar) untuk mendapatkan akses ke instantiasi tipe generik pada saat runtime , membuktikan bahwa memang ada sesuatu dalam bytecode.Poin yang lebih menarik (yang belum diangkat) adalah bahwa mengimplementasikan obat generik menggunakan erasure menawarkan fleksibilitas yang lebih banyak dalam apa yang dapat dicapai oleh sistem tipe tingkat tinggi. Contoh yang baik dari ini adalah implementasi JVM Scala vs CLR. Pada JVM, dimungkinkan untuk menerapkan jenis yang lebih tinggi secara langsung karena fakta bahwa JVM sendiri tidak memberlakukan pembatasan pada tipe generik (karena "tipe" ini secara efektif tidak ada). Ini kontras dengan CLR, yang memiliki pengetahuan runtime instantiations parameter. Karena itu, CLR itu sendiri harus memiliki beberapa konsep tentang bagaimana obat generik harus digunakan, membatalkan upaya untuk memperluas sistem dengan aturan yang tidak terduga. Akibatnya, Scala yang lebih tinggi pada CLR diimplementasikan menggunakan bentuk erasure aneh yang ditiru dalam kompiler itu sendiri,
Penghapusan mungkin tidak nyaman ketika Anda ingin melakukan hal-hal nakal saat runtime, tetapi memang menawarkan fleksibilitas paling besar kepada penulis kompiler. Saya menduga itu adalah bagian dari mengapa itu tidak akan pergi dalam waktu dekat.
sumber
Seperti yang saya pahami (menjadi seorang .NET guy) JVM tidak memiliki konsep generik, sehingga kompiler menggantikan parameter tipe dengan Object dan melakukan semua casting untuk Anda.
Ini berarti bahwa generik Java tidak lain adalah gula sintaksis dan tidak menawarkan peningkatan kinerja apa pun untuk tipe nilai yang memerlukan tinju / unboxing ketika disahkan oleh referensi.
sumber
Ada penjelasan bagus. Saya hanya menambahkan contoh untuk menunjukkan bagaimana tipe erasure bekerja dengan dekompiler.
Kelas asli,
Kode yang didekompilasi dari bytecode-nya,
sumber
Mengapa menggunakan Generices
Singkatnya, generik memungkinkan tipe (kelas dan antarmuka) menjadi parameter ketika mendefinisikan kelas, antarmuka, dan metode. Sama seperti parameter formal yang lebih akrab digunakan dalam deklarasi metode, ketik parameter memberikan cara bagi Anda untuk menggunakan kembali kode yang sama dengan input yang berbeda. Perbedaannya adalah input ke parameter formal adalah nilai, sedangkan input untuk mengetik parameter adalah tipe. ode yang menggunakan obat generik memiliki banyak manfaat dibandingkan kode non-generik:
Apa itu Penghapusan Jenis
Generik diperkenalkan ke bahasa Jawa untuk menyediakan pemeriksaan tipe yang lebih ketat pada waktu kompilasi dan untuk mendukung pemrograman generik. Untuk menerapkan obat generik, kompiler Java menerapkan tipe penghapusan untuk:
[NB] -Apa itu metode jembatan? Singkatnya, Dalam kasus antarmuka berparameter seperti
Comparable<T>
, ini dapat menyebabkan metode tambahan dimasukkan oleh kompiler; metode tambahan ini disebut jembatan.Cara Kerja Penghapusan
Penghapusan tipe didefinisikan sebagai berikut: jatuhkan semua tipe parameter dari tipe parameter, dan ganti variabel tipe apa pun dengan penghapusan batasnya, atau dengan Objek jika tidak ada batasnya, atau dengan penghapusan batas paling kiri jika ia memiliki banyak batas. Berikut ini beberapa contohnya:
List<Integer>
,List<String>
danList<List<String>>
adalahList
.List<Integer>[]
adalahList[]
.List
itu sendiri, sama untuk semua jenis mentah.Integer
itu sendiri, sama untuk semua jenis tanpa parameter tipe.T
dalam definisiasList
adalahObject
, karenaT
tidak memiliki batas.T
dalam definisimax
adalahComparable
, karenaT
telah terikatComparable<? super T>
.T
dalam definisi akhirmax
adalahObject
, karenaT
telah terikatObject
&Comparable<T>
dan kami mengambil penghapusan dari batas paling kiri.Perlu hati-hati saat menggunakan obat generik
Di Jawa, dua metode berbeda tidak dapat memiliki tanda tangan yang sama. Karena obat generik diimplementasikan oleh penghapusan, maka juga mengikuti bahwa dua metode yang berbeda tidak dapat memiliki tanda tangan dengan penghapusan yang sama. Kelas tidak dapat membebani dua metode yang tanda tangannya memiliki penghapusan yang sama, dan kelas tidak dapat mengimplementasikan dua antarmuka yang memiliki penghapusan yang sama.
Kami bermaksud kode ini berfungsi sebagai berikut:
Namun, dalam hal ini penghapusan tanda tangan dari kedua metode identik:
Oleh karena itu, bentrokan nama dilaporkan pada waktu kompilasi. Tidak mungkin untuk memberikan kedua metode nama yang sama dan mencoba untuk membedakan antara mereka dengan overloading, karena setelah penghapusan tidak mungkin untuk membedakan satu metode panggilan dari yang lain.
Semoga Pembaca akan menikmati :)
sumber