Saya kadang-kadang mendengar bahwa dengan obat generik, Java tidak melakukannya dengan benar. (referensi terdekat, di sini )
Maafkan saya yang belum berpengalaman, tapi apa yang bisa membuat mereka lebih baik?
Saya kadang-kadang mendengar bahwa dengan obat generik, Java tidak melakukannya dengan benar. (referensi terdekat, di sini )
Maafkan saya yang belum berpengalaman, tapi apa yang bisa membuat mereka lebih baik?
Jawaban:
Buruk:
List<byte>
benar - benar didukung oleh byte[]
misalnya, dan tidak diperlukan tinju)Baik:
Masalah terbesar adalah bahwa Java generik adalah satu-satunya hal yang waktu kompilasi, dan Anda dapat menumbangkannya pada saat run-time. C # dipuji karena melakukan lebih banyak pemeriksaan waktu berjalan. Ada beberapa diskusi yang sangat bagus dalam posting ini , dan ini terhubung ke diskusi lain.
Class
objek.
Masalah utamanya adalah Java sebenarnya tidak memiliki generik saat runtime. Ini adalah fitur waktu kompilasi.
Ketika Anda membuat kelas generik di Java, mereka menggunakan metode yang disebut "Type Erasure" untuk benar-benar menghapus semua tipe generik dari kelas dan pada dasarnya menggantinya dengan Object. Versi tinggi dari obat generik adalah bahwa kompilator hanya menyisipkan cast ke jenis generik yang ditentukan setiap kali muncul di badan metode.
Ini memiliki banyak kerugian. Salah satu yang terbesar, IMHO, adalah Anda tidak dapat menggunakan refleksi untuk memeriksa tipe generik. Jenis sebenarnya tidak generik dalam kode byte dan karenanya tidak dapat diperiksa sebagai generik.
Gambaran umum yang bagus tentang perbedaan di sini: http://www.jprl.com/Blog/archive/development/2007/Aug-31.html
(1) menyebabkan beberapa perilaku yang sangat aneh. Contoh terbaik yang dapat saya pikirkan adalah. Menganggap:
public class MyClass<T> {
T getStuff() { ... }
List<String> getOtherStuff() { ... }
}
kemudian nyatakan dua variabel:
MyClass<T> m1 = ...
MyClass m2 = ...
Sekarang hubungi getOtherStuff()
:
List<String> list1 = m1.getOtherStuff();
List<String> list2 = m2.getOtherStuff();
Argumen kedua memiliki tipe generik yang dilepaskan oleh kompilator karena ini adalah tipe mentah (artinya tipe berparameter tidak disediakan) meskipun tidak ada hubungannya dengan tipe berparameter.
Saya juga akan menyebutkan deklarasi favorit saya dari JDK:
public class Enum<T extends Enum<T>>
Terlepas dari wildcarding (yang merupakan tas campuran) saya hanya berpikir .Net generik lebih baik.
public class Redundancy<R extends Redundancy<R>>
;)
The expression of type List needs unchecked conversion to conform to List<String>
Enum<T extends Enum<T>>
mungkin tampak aneh / berlebihan pada awalnya tetapi sebenarnya cukup menarik, setidaknya dalam batasan Java / generiknya. Enum memiliki values()
metode statis yang memberikan larik elemennya yang diketik sebagai enum, bukan Enum
, dan jenis itu ditentukan oleh parameter generik, artinya Anda inginkan Enum<T>
. Tentu saja, pengetikan itu hanya masuk akal dalam konteks tipe enumerasi, dan semua enum adalah subkelas Enum
, jadi Anda inginkan Enum<T extends Enum>
. Namun, Java tidak suka mencampurkan tipe mentah dengan generik, sehingga Enum<T extends Enum<T>>
konsistensi.
Saya akan memberikan pendapat yang sangat kontroversial. Generik memperumit bahasa dan memperumit kode. Misalnya, saya memiliki peta yang memetakan string ke daftar string. Di masa lalu, saya dapat menyatakan ini sebagai
Map someMap;
Sekarang, saya harus menyatakannya sebagai
Map<String, List<String>> someMap;
Dan setiap kali saya menyebarkannya ke beberapa metode, saya harus mengulangi pernyataan panjang yang besar itu lagi. Menurut pendapat saya, semua pengetikan ekstra itu mengalihkan perhatian pengembang dan membawanya keluar dari "zona". Selain itu, ketika kode diisi dengan banyak celah, terkadang sulit untuk kembali lagi nanti dan dengan cepat menyaring semua celah untuk menemukan logika penting.
Java sudah memiliki reputasi buruk sebagai salah satu bahasa paling verbose yang umum digunakan, dan bahasa generik hanya menambah masalah itu.
Dan apa yang sebenarnya Anda beli untuk semua verbositas ekstra itu? Berapa kali Anda benar-benar mengalami masalah saat seseorang memasukkan Integer ke dalam koleksi yang seharusnya menyimpan String, atau saat seseorang mencoba menarik String dari koleksi Integer? Dalam 10 tahun pengalaman saya bekerja membangun aplikasi Java komersial, ini tidak pernah menjadi sumber kesalahan yang besar. Jadi, saya tidak begitu yakin apa yang Anda dapatkan untuk verbositas ekstra. Bagi saya, itu benar-benar hanya sebagai beban birokrasi ekstra.
Sekarang saya akan menjadi sangat kontroversial. Apa yang saya lihat sebagai masalah terbesar dengan koleksi di Java 1.4 adalah perlunya typecast di mana-mana. Saya melihat typecasts itu sebagai tambahan, verbose cruft yang memiliki banyak masalah yang sama seperti obat generik. Jadi, misalnya, saya tidak bisa begitu saja
List someList = someMap.get("some key");
Saya harus melakukan
List someList = (List) someMap.get("some key");
Alasannya, tentu saja, adalah get () mengembalikan Object yang merupakan supertipe List. Jadi tugas tidak dapat dilakukan tanpa typecast. Sekali lagi, pikirkan tentang seberapa besar aturan itu membeli Anda. Dari pengalaman saya, tidak banyak.
Saya pikir Java akan jauh lebih baik jika 1) tidak menambahkan generik tetapi 2) sebaliknya mengizinkan casting implisit dari supertipe ke subtipe. Biarkan cast yang salah ditangkap pada waktu proses. Maka saya bisa memiliki kesederhanaan dalam mendefinisikan
Map someMap;
dan kemudian melakukan
List someList = someMap.get("some key");
semua kekurangannya akan hilang, dan saya pikir saya tidak akan memperkenalkan sumber bug baru yang besar ke dalam kode saya.
Efek samping lain dari mereka adalah waktu kompilasi dan bukan waktu berjalan adalah Anda tidak dapat memanggil konstruktor dari tipe generik. Jadi Anda tidak dapat menggunakannya untuk mengimplementasikan pabrik generik ...
public class MyClass {
public T getStuff() {
return new T();
}
}
--jeffk ++
Generik Java diperiksa kebenarannya pada waktu kompilasi dan kemudian semua informasi tipe dihapus (proses ini disebut penghapusan tipe . Jadi, generik List<Integer>
akan direduksi menjadi tipe mentahnya , non-generik List
, yang dapat berisi objek kelas arbitrer.
Ini menghasilkan kemampuan untuk memasukkan objek arbitrer ke daftar pada waktu proses, serta sekarang tidak mungkin untuk mengetahui jenis apa yang digunakan sebagai parameter generik. Yang terakhir pada gilirannya menghasilkan
ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if(li.getClass() == lf.getClass()) // evaluates to true
System.out.println("Equal");
Saya berharap ini adalah wiki sehingga saya bisa menambahkan ke orang lain ... tapi ...
Masalah:
<
? Extends MyObject >
[], tapi saya tidak diizinkan)Mengabaikan seluruh jenis kekacauan penghapusan, obat generik seperti yang ditentukan tidak berfungsi.
Ini mengkompilasi:
List<Integer> x = Collections.emptyList();
Tetapi ini adalah kesalahan sintaks:
foo(Collections.emptyList());
Dimana foo didefinisikan sebagai:
void foo(List<Integer> x) { /* method body not important */ }
Jadi apakah pemeriksaan jenis ekspresi bergantung pada apakah ia ditugaskan ke variabel lokal atau parameter sebenarnya dari panggilan metode. Gila apa itu?
Pengenalan generik ke Java adalah tugas yang sulit karena arsitek mencoba menyeimbangkan fungsionalitas, kemudahan penggunaan, dan kompatibilitas mundur dengan kode lama. Sangat diharapkan, kompromi harus dilakukan.
Ada beberapa yang juga merasa bahwa implementasi Java generik meningkatkan kompleksitas bahasa ke tingkat yang tidak dapat diterima (lihat " Generics Anggap Berbahaya " dari Ken Arnold ). FAQ Generik Angelika Langer memberikan ide yang cukup bagus tentang bagaimana hal-hal bisa menjadi rumit.
Java tidak memberlakukan Generik pada waktu proses, hanya pada waktu kompilasi.
Ini berarti Anda dapat melakukan hal-hal menarik seperti menambahkan jenis yang salah ke Koleksi generik.
Java generik hanya untuk waktu kompilasi dan dikompilasi menjadi kode non-generik. Di C #, MSIL terkompilasi sebenarnya adalah generik. Ini memiliki implikasi yang sangat besar pada kinerja karena Java masih melakukan transmisi selama runtime. Lihat di sini untuk lebih lanjut .
Jika Anda mendengarkan Java Posse # 279 - Wawancara dengan Joe Darcy dan Alex Buckley , mereka membicarakan masalah ini. Itu juga tertaut ke posting blog Neal Gafter berjudul Reified Generics for Java yang mengatakan:
Banyak orang tidak puas dengan batasan yang disebabkan oleh cara generik diimplementasikan di Java. Secara khusus, mereka tidak senang bahwa parameter tipe generik tidak direifikasi: mereka tidak tersedia pada waktu proses. Generik diimplementasikan menggunakan erasure, di mana parameter tipe generik dibuang begitu saja saat runtime.
Posting blog itu, merujuk pada entri lama, Puzzling Through Erasure: bagian jawaban , yang menekankan poin tentang kompatibilitas migrasi dalam persyaratan.
Tujuannya adalah untuk menyediakan kompatibilitas ke belakang dari kode sumber dan objek, dan juga kompatibilitas migrasi.