Saya membaca tentang penghapusan tipe Java di situs web Oracle .
Kapan penghapusan tipe terjadi? Pada waktu kompilasi atau runtime? Kapan kelas dimuat? Kapan kelas dipakai?
Banyak situs (termasuk tutorial resmi yang disebutkan di atas) mengatakan penghapusan tipe terjadi pada waktu kompilasi. Jika informasi jenis dihapus sepenuhnya pada waktu kompilasi, bagaimana JDK memeriksa kompatibilitas jenis ketika metode menggunakan obat-obatan dipanggil tanpa informasi jenis atau informasi jenis yang salah?
Pertimbangkan contoh berikut: Katakanlah kelas A
memiliki metode empty(Box<? extends Number> b)
,. Kami mengkompilasi A.java
dan mendapatkan file kelas A.class
.
public class A {
public static void empty(Box<? extends Number> b) {}
}
public class Box<T> {}
Sekarang kita buat kelas lain B
yang memanggil metode empty
dengan argumen non-parameter (tipe mentah): empty(new Box())
. Jika kita kompilasi B.java
dengan A.class
di classpath, javac cukup pintar untuk membangkitkan peringatan. Jadi A.class
ada beberapa jenis informasi yang tersimpan di dalamnya.
public class B {
public static void invoke() {
// java: unchecked method invocation:
// method empty in class A is applied to given types
// required: Box<? extends java.lang.Number>
// found: Box
// java: unchecked conversion
// required: Box<? extends java.lang.Number>
// found: Box
A.empty(new Box());
}
}
Dugaan saya adalah bahwa penghapusan tipe terjadi ketika kelas dimuat, tetapi itu hanya dugaan. Jadi kapan itu terjadi?
sumber
Jawaban:
Penghapusan tipe berlaku untuk penggunaan obat generik. Jelas ada metadata di file kelas untuk mengatakan apakah metode / tipe adalah generik, dan apa batasannya, dll. Tetapi ketika generik digunakan , mereka dikonversi menjadi pemeriksaan waktu kompilasi dan waktu eksekusi. Jadi kode ini:
dikompilasi menjadi
Pada waktu eksekusi tidak ada cara untuk mengetahui bahwa
T=String
untuk objek daftar - informasi itu hilang.... tetapi
List<T>
antarmuka itu sendiri masih mengiklankan dirinya sebagai generik.EDIT: Hanya untuk memperjelas, kompiler tidak menyimpan informasi tentang variabel menjadi
List<String>
- tetapi Anda masih tidak bisa mengetahui bahwaT=String
untuk objek daftar itu sendiri.sumber
List<? extends InputStream>
bagaimana Anda bisa tahu tipe apa itu ketika dibuat? Bahkan jika Anda dapat mengetahui jenis bidang referensi yang telah disimpan, mengapa Anda harus melakukannya? Mengapa Anda bisa mendapatkan semua informasi tentang objek pada waktu eksekusi, tetapi tidak pada argumen tipe generiknya? Anda tampaknya mencoba untuk membuat penghapusan tipe menjadi hal kecil ini yang tidak mempengaruhi pengembang sebenarnya - sedangkan saya menemukan itu menjadi masalah yang sangat signifikan dalam beberapa kasus.Compiler bertanggung jawab untuk memahami Generics pada waktu kompilasi. Compiler juga bertanggung jawab untuk membuang "pemahaman" kelas generik ini, dalam proses yang kita sebut penghapusan tipe . Semua terjadi pada waktu kompilasi.
Catatan: Bertentangan dengan kepercayaan mayoritas pengembang Java, dimungkinkan untuk menyimpan informasi tipe waktu kompilasi dan mengambil informasi ini pada saat runtime, meskipun dengan cara yang sangat terbatas. Dengan kata lain: Java memang menyediakan reified generics dengan cara yang sangat terbatas .
Mengenai penghapusan jenis
Perhatikan bahwa, pada waktu kompilasi, kompiler memiliki informasi tipe lengkap yang tersedia tetapi informasi ini sengaja dihapus secara umum ketika kode byte dihasilkan, dalam proses yang dikenal sebagai tipe erasure . Ini dilakukan dengan cara ini karena masalah kompatibilitas: Tujuan dari perancang bahasa adalah menyediakan kompatibilitas kode sumber penuh dan kompatibilitas kode byte penuh antara versi platform. Jika itu diterapkan secara berbeda, Anda harus mengkompilasi ulang aplikasi lawas Anda ketika bermigrasi ke versi platform yang lebih baru. Cara itu dilakukan, semua tanda tangan metode dipertahankan (kompatibilitas kode sumber) dan Anda tidak perlu mengkompilasi ulang apa pun (kompatibilitas biner).
Mengenai reified generics di Java
Jika Anda perlu menyimpan informasi tipe waktu kompilasi, Anda harus menggunakan kelas anonim. Intinya adalah: dalam kasus khusus kelas anonim, dimungkinkan untuk mengambil informasi tipe waktu kompilasi penuh pada saat runtime yang, dengan kata lain berarti: reified generics. Ini berarti bahwa kompiler tidak membuang informasi jenis ketika kelas anonim terlibat; informasi ini disimpan dalam kode biner yang dihasilkan dan sistem runtime memungkinkan Anda untuk mengambil informasi ini.
Saya telah menulis artikel tentang subjek ini:
https://rgomes.info/using-typetokens-to-retrieve-generic-parameters/
Catatan tentang teknik yang dijelaskan dalam artikel di atas adalah bahwa teknik ini tidak jelas untuk sebagian besar pengembang. Meskipun bekerja dan bekerja dengan baik, sebagian besar pengembang merasa bingung atau tidak nyaman dengan teknik ini. Jika Anda memiliki basis kode bersama atau berencana untuk merilis kode Anda kepada publik, saya tidak merekomendasikan teknik di atas. Di sisi lain, jika Anda adalah satu-satunya pengguna kode Anda, Anda dapat memanfaatkan kekuatan yang diberikan teknik ini kepada Anda.
Kode sampel
Artikel di atas memiliki tautan ke kode sampel.
sumber
new Box<String>() {};
bukan dalam kasus akses tidak langsungvoid foo(T) {...new Box<T>() {};...}
karena kompiler tidak menyimpan informasi tipe untuk deklarasi metode terlampir.Jika Anda memiliki bidang yang merupakan tipe generik, parameter tipenya dikompilasi ke dalam kelas.
Jika Anda memiliki metode yang mengambil atau mengembalikan tipe generik, parameter tipe tersebut dikompilasi ke dalam kelas.
Informasi ini adalah apa yang menggunakan compiler untuk memberitahu Anda bahwa Anda tidak dapat lulus
Box<String>
denganempty(Box<T extends Number>)
metode.API yang rumit, tetapi Anda dapat memeriksa informasi jenis ini melalui refleksi API dengan metode seperti
getGenericParameterTypes
,getGenericReturnType
, dan, untuk bidang,getGenericType
.Jika Anda memiliki kode yang menggunakan tipe generik, kompiler menyisipkan gips yang diperlukan (di pemanggil) untuk memeriksa jenis. Objek generik itu sendiri hanyalah tipe mentah; tipe yang diparameterisasi "dihapus". Jadi, saat Anda membuat
new Box<Integer>()
, tidak ada informasi tentangInteger
kelas diBox
objek.FAQ Angelika Langer adalah referensi terbaik yang pernah saya lihat untuk Java Generics.
sumber
Generik dalam Bahasa Jawa adalah panduan yang sangat bagus tentang topik ini.
Jadi, ini pada waktu kompilasi. JVM tidak akan pernah tahu yang
ArrayList
Anda gunakan.Saya juga merekomendasikan jawaban Tn. Skeet tentang Apa konsep penghapusan dalam obat generik di Jawa?
sumber
Penghapusan tipe terjadi pada waktu kompilasi. Apa yang dimaksud dengan penghapusan tipe adalah bahwa ia akan melupakan jenis generik, bukan tentang setiap jenis. Selain itu, masih akan ada metadata tentang jenis yang generik. Sebagai contoh
dikonversi menjadi
pada waktu kompilasi. Anda mungkin mendapatkan peringatan bukan karena kompiler tahu tentang jenis apa yang merupakan generik, tetapi sebaliknya, karena tidak cukup tahu sehingga tidak dapat menjamin keamanan jenis.
Selain itu, kompiler mempertahankan informasi tipe tentang parameter pada pemanggilan metode, yang dapat Anda ambil melalui refleksi.
Panduan ini adalah yang terbaik yang saya temukan pada subjek.
sumber
Istilah "type erasure" sebenarnya bukan deskripsi yang benar tentang masalah Java dengan obat generik. Penghapusan tipe bukan per hal yang buruk, memang sangat diperlukan untuk kinerja dan sering digunakan dalam beberapa bahasa seperti C ++, Haskell, D.
Sebelum Anda jijik, harap ingat definisi penghapusan tipe yang benar dari Wiki
Apa itu tipe erasure?
Penghapusan tipe berarti membuang tag tipe yang dibuat pada waktu desain atau tag tipe yang disimpulkan pada waktu kompilasi sehingga program yang dikompilasi dalam kode biner tidak mengandung tag tipe apa pun. Dan ini adalah kasus untuk setiap bahasa pemrograman yang dikompilasi ke kode biner kecuali dalam beberapa kasus di mana Anda membutuhkan tag runtime. Pengecualian ini termasuk misalnya semua tipe eksistensial (Tipe Referensi Java yang subtipe, Semua Jenis dalam banyak bahasa, Tipe Union). Alasan penghapusan jenis adalah bahwa program ditransformasikan ke bahasa yang dalam beberapa jenis uni-diketik (bahasa biner hanya memungkinkan bit) karena jenis adalah abstraksi saja dan menyatakan struktur untuk nilai-nilainya dan semantik yang tepat untuk menanganinya.
Jadi ini balasannya, hal yang wajar.
Masalah Java berbeda dan disebabkan oleh bagaimana ia direvisi.
Pernyataan yang sering dibuat tentang Java tidak memiliki reified generics juga salah.
Java memang memverifikasi, tetapi dengan cara yang salah karena kompatibilitas ke belakang.
Apa itu reifikasi?
Dari Wiki kami
Reifikasi berarti mengubah sesuatu yang abstrak (Tipe Parametrik) menjadi sesuatu yang konkret (Jenis Beton) dengan spesialisasi.
Kami menggambarkan ini dengan contoh sederhana:
Daftar Array dengan definisi:
adalah abstraksi, secara terperinci sebuah konstruktor tipe, yang akan "diverifikasi" ketika dikhususkan dengan tipe beton, katakan Integer:
di mana
ArrayList<Integer>
sebenarnya tipe.Tapi ini persis hal yang bukan Java !!! , sebagai gantinya mereka merevisi tipe yang abstrak secara konstan dengan batasannya, yaitu memproduksi tipe beton yang sama tanpa parameter yang diteruskan untuk spesialisasi:
yang di sini diverifikasi dengan Objek terikat implisit (
ArrayList<T extends Object>
==ArrayList<T>
).Meskipun demikian itu membuat array generik tidak dapat digunakan dan menyebabkan beberapa kesalahan aneh untuk tipe mentah:
itu menyebabkan banyak ambiguitas
lihat fungsi yang sama:
dan karena itu overloading metode generik tidak dapat digunakan di Jawa.
sumber
Saya sudah menemukan tipe erasure di Android. Dalam produksi kami menggunakan gradle dengan opsi minify. Setelah minifikasi, saya mendapat pengecualian fatal. Saya telah membuat fungsi sederhana untuk menunjukkan rantai pewarisan objek saya:
Dan ada dua hasil fungsi ini:
Bukan kode yang diperkecil:
Kode yang diperkecil:
Jadi, dalam kode minified, kelas parametrized aktual diganti dengan tipe kelas mentah tanpa informasi tipe apa pun. Sebagai solusi untuk proyek saya, saya menghapus semua panggilan refleksi dan menggantinya dengan tipe params eksplisit yang diteruskan dalam argumen fungsi.
sumber