Apa yang salah dengan obat generik Java? [Tutup]

49

Saya telah melihat beberapa kali di posting situs ini yang mengutuk implementasi generik Java. Sekarang, saya dapat dengan jujur ​​mengatakan bahwa saya tidak memiliki masalah dengan menggunakannya. Namun, saya belum mencoba membuat kelas generik sendiri. Jadi, apa masalah Anda dengan dukungan generik Java?

Michael K.
sumber
1
Lihat juga stackoverflow.com/questions/31693/...
Bill Michell
seperti yang dikatakan Clinton Begin ("orang iBatis"), "mereka tidak bekerja ..."
agas

Jawaban:

54

Implementasi generik Java menggunakan tipe erasure . Ini berarti bahwa koleksi generik Anda yang sangat diketik sebenarnya bertipe Objectsaat runtime. Ini memiliki beberapa pertimbangan kinerja karena itu berarti tipe primitif harus kotak ketika ditambahkan ke koleksi generik. Tentu saja manfaat dari kebenaran jenis waktu kompilasi lebih besar daripada kekonyolan umum dari penghapusan jenis dan fokus obsesif pada kompatibilitas ke belakang.

Kekacauan Kekacauan
sumber
4
Hanya untuk menambahkan, karena penghapusan tipe, mengambil informasi umum pada saat runtime tidak mungkin kecuali Anda menggunakan sesuatu seperti TypeLiteral google-guice.googlecode.com/svn/trunk/javadoc/com/google/inject/…
Jeremy Heiler
43
Artinyanew ArrayList<String>.getClass() == new ArrayList<Integer>.getClass()
Catatan untuk diri sendiri - pikirkan nama
9
@Tidak, tidak, tidak. Dan C ++ menggunakan pendekatan yang berbeda untuk metaprogramming yang disebut templates. Ini menggunakan bebek-mengetik dan Anda dapat melakukan hal-hal yang bermanfaat seperti template<T> T add(T v1, T v2) { return v1->add(v2); }dan di sana Anda memiliki cara yang benar-benar generik untuk menciptakan fungsi yang merupakan adddua hal, apa pun itu, mereka hanya perlu memiliki metode yang dinamai add()dengan satu parameter.
Trinidad
7
@ Job itu adalah kode yang dihasilkan, memang. Template dimaksudkan untuk menggantikan preprocessor C dan untuk melakukannya mereka harus dapat membuat beberapa trik yang sangat pintar dan dengan sendirinya bahasa Turing-lengkap. Obat generik Java hanya gula untuk wadah yang aman untuk jenis, dan obat generik C # lebih baik tetapi masih berupa templat C ++ orang miskin.
Trinidad
3
@Job AFAIK, Java generics tidak menghasilkan kelas untuk setiap jenis generik baru, itu hanya menambahkan typecasts ke kode yang menggunakan metode generik, jadi itu sebenarnya bukan meta-pemrograman IMO. Templat C # menghasilkan kode baru untuk setiap jenis umum, yaitu, jika Anda menggunakan Daftar <int> dan Daftar <double> di dunia C # akan menghasilkan kode untuk masing-masingnya. Dibandingkan dengan templat, generik C # masih perlu mengetahui jenis yang dapat Anda beri makan. Anda tidak dapat menerapkan Addcontoh sederhana yang saya berikan, tidak ada cara tanpa mengetahui sebelumnya kelas mana yang dapat diterapkan, yang merupakan kelemahan.
Trinidad
26

Perhatikan bahwa jawaban yang telah disediakan berkonsentrasi pada kombinasi Java-the-language, JVM dan Java Class Library.

Tidak ada yang salah dengan obat generik Java sejauh menyangkut bahasa Jawa. Seperti yang dijelaskan dalam C # vs generics Java , generics Java cukup baik pada level bahasa 1 .

Apa yang suboptimal adalah bahwa JVM tidak mendukung obat generik secara langsung, yang memiliki beberapa konsekuensi utama:

  • bagian yang berhubungan dengan refleksi dari Perpustakaan Kelas tidak dapat mengekspos informasi tipe lengkap yang tersedia dalam bahasa Jawa
  • beberapa penalti kinerja terlibat

Saya kira perbedaan yang saya buat adalah yang sangat bagus karena bahasa Jawa dikompilasi di mana-mana dengan JVM sebagai target dan Perpustakaan Kelas Jawa sebagai perpustakaan inti.

1 Dengan kemungkinan pengecualian wildcard, yang diyakini membuat inferensi jenis tidak dapat diputuskan dalam kasus umum. Ini adalah perbedaan utama antara C # dan generik Java yang tidak sering disebutkan sama sekali. Terima kasih, Antimony.

Roman Starkov
sumber
14
JVM tidak perlu mendukung obat generik. Ini akan sama dengan mengatakan bahwa C ++ tidak memiliki templat karena mesin ix86 real tidak mendukung templat, yang salah. Masalahnya adalah pendekatan yang dilakukan oleh kompiler untuk mengimplementasikan tipe generik. Dan lagi, template C ++ tidak melibatkan penalti pada saat run-time, tidak ada refleksi yang dilakukan sama sekali, saya kira satu-satunya alasan yang perlu dilakukan di Jawa hanyalah desain bahasa yang buruk.
Trinidad
2
@Trinidad tapi saya tidak mengatakan Java tidak memiliki obat generik, jadi saya tidak melihat bagaimana itu sama. Dan ya, JVM tidak perlu mendukung mereka, sama seperti C ++ tidak perlu mengoptimalkan kode. Tetapi tidak mengoptimalkannya tentu akan dianggap sebagai "sesuatu yang salah".
Roman Starkov
3
Apa yang saya katakan adalah bahwa tidak perlu JVM untuk memiliki dukungan langsung untuk obat generik untuk dapat menggunakan refleksi pada mereka. Orang lain telah melakukannya sehingga dapat dilakukan, masalahnya adalah bahwa Java tidak menghasilkan kelas baru dari generik, hal yang tidak reifikasi.
Trinidad
4
@Trinidad: Dalam C ++, dengan asumsi kompiler diberikan cukup waktu dan memori, templat dapat digunakan untuk melakukan apa saja dan segala sesuatu yang dapat dilakukan dengan informasi yang tersedia pada waktu kompilasi (karena kompilasi templat adalah Turing-selesai), tetapi ada tidak ada cara untuk membuat templat menggunakan informasi yang tidak tersedia sebelum program dijalankan. Di .net, sebaliknya, adalah mungkin bagi suatu program untuk membuat tipe berdasarkan input; itu akan cukup mudah untuk menulis sebuah program di mana jumlah berbagai jenis yang dapat dibuat melebihi jumlah elektron di alam semesta.
supercat
2
@Trinidad: Jelas, tidak ada satu pun eksekusi program yang dapat membuat semua jenis (atau, memang, apa pun di luar sebagian kecil dari mereka), tetapi intinya adalah tidak harus. Hanya perlu membuat jenis yang benar-benar digunakan. Seseorang dapat memiliki program yang dapat menerima nomor arbitrer apa pun (mis. 8675309) Dan darinya membuat jenis yang unik untuk nomor itu (mis. Z8<Z6<Z7<Z5<Z3<Z0<Z9>>>>>>>), Yang akan memiliki anggota yang berbeda dari jenis lainnya. Dalam C ++, semua tipe yang dapat dihasilkan dari input apa pun harus diproduksi pada waktu kompilasi.
supercat
13

Kritik yang biasa adalah kurangnya reifikasi. Artinya objek pada saat runtime tidak mengandung informasi tentang argumen generiknya (meskipun informasi tersebut masih ada pada bidang, metode, konstruktor dan kelas dan antarmuka yang diperluas). Ini berarti Anda dapat menggunakan, katakanlah, ArrayList<String>untuk List<File>. Kompiler akan memberi Anda peringatan, tetapi juga akan memperingatkan Anda jika Anda ArrayList<String>menetapkannya sebagai Objectreferensi dan kemudian melemparkannya ke List<String>. Sisi baiknya adalah bahwa dengan obat generik Anda mungkin tidak boleh melakukan casting, kinerjanya lebih baik tanpa data yang tidak perlu dan, tentu saja, kompatibilitas ke belakang.

Beberapa orang mengeluh bahwa Anda tidak dapat kelebihan beban berdasarkan argumen umum ( void fn(Set<String>)dan void fn(Set<File>)). Anda harus menggunakan nama metode yang lebih baik. Perhatikan bahwa kelebihan beban ini tidak memerlukan reifikasi, karena kelebihan muatan adalah masalah waktu kompilasi yang statis.

Tipe primitif tidak bekerja dengan obat generik.

Kartu liar dan batasnya cukup rumit. Mereka sangat berguna. Jika Java lebih memilih immutability dan antarmuka tell-don't-ask, maka deklarasi sisi daripada penggunaan generik akan lebih tepat.

Tom Hawtin - tackline
sumber
"Perhatikan bahwa kelebihan beban ini tidak memerlukan reifikasi, karena kelebihan muatan adalah masalah waktu kompilasi yang statis." Bukan? Bagaimana cara kerjanya tanpa reifikasi? Bukankah JVM harus tahu pada saat runtime jenis set yang Anda miliki, untuk mengetahui metode mana yang harus dihubungi?
MatrixFrog
2
@ MatrixFrog No. Seperti yang saya katakan, kelebihan adalah masalah waktu kompilasi. Compiler menggunakan tipe statis dari ekspresi untuk memilih overload tertentu, yang kemudian metode tersebut ditulis ke file kelas. Jika sudah, Object cs = new char[] { 'H', 'i' }; System.out.println(cs);cetaklah omong kosong. Ubah jenis cske char[]dan Anda akan mendapatkannya Hi.
Tom Hawtin - tackline
10

Java generics menyedot karena Anda tidak dapat melakukan hal berikut:

public class AsyncAdapter<Parser,Adapter> extends AsyncTask<String,Integer,Adapter> {
    proptected Adapter doInBackground(String... keywords) {
      Parser p = new Parser(keywords[0]); // this is an error
      /* some more stuff I was hoping for but couldn't do because
         the compiler wouldn't let me
      */
    }
}

Anda tidak perlu memotong setiap kelas dalam kode di atas untuk membuatnya berfungsi sebagaimana mestinya jika generik sebenarnya adalah parameter kelas generik.

davidk01
sumber
Anda tidak perlu memotong setiap kelas. Anda hanya perlu menambahkan kelas pabrik yang sesuai dan menyuntikkannya ke AsyncAdapter. (Dan pada dasarnya ini bukan tentang obat generik, tetapi fakta bahwa konstruktor tidak diwarisi di Jawa).
Peter Taylor
13
@PeterTaylor: Kelas pabrik yang cocok hanya mendorong semua pelat ke beberapa kekacauan lain dan masih tidak menyelesaikan masalah. Sekarang setiap kali saya ingin membuat instance baru Parsersaya perlu membuat kode pabrik lebih berbelit-belit. Sedangkan jika "generik" benar-benar berarti generik maka tidak akan ada kebutuhan untuk pabrik dan semua omong kosong lainnya yang menggunakan nama pola desain. Itu salah satu dari banyak alasan saya lebih suka bekerja dalam bahasa yang dinamis.
davidk01
2
Terkadang serupa di C ++, di mana Anda tidak bisa melewatkan alamat konstruktor, saat menggunakan templat + virtual - Anda bisa menggunakan functor pabrik sebagai gantinya.
Mark K Cowan
Java menyebalkan karena Anda tidak dapat menulis "proptected" sebelum nama metode? Kasihan kamu. Tetap menggunakan bahasa yang dinamis. Dan berhati-hatilah pada Haskell.
fdreger
5
  • peringatan kompiler untuk parameter generik yang hilang yang tidak ada gunanya membuat bahasa menjadi tidak berarti, misalnya public String getName(Class<?> klazz){ return klazz.getName();}

  • Generik tidak bermain bagus dengan array

  • Informasi jenis yang hilang membuat refleksi berantakan casting dan lakban.

Steve B.
sumber
Saya merasa kesal ketika mendapat peringatan untuk menggunakan, HashMapbukan HashMap<String, String>.
Michael K
1
@Michael tapi itu sebenarnya harus digunakan dengan obat generik ...
alternatif
Masalah array menuju reifiability. Lihat buku Java Generics (Alligator) untuk penjelasan.
ncmathsadist
5

Saya pikir jawaban lain telah mengatakan hal ini sampai taraf tertentu, tetapi tidak terlalu jelas. Salah satu masalah dengan obat generik adalah kehilangan jenis obat generik selama proses refleksi. Jadi misalnya:

List<String> arr = new ArrayList<String>();
assertTrue( ArrayList.class, arr.getClass() );
TypeVarible[] types = arr.getClass().getTypedVariables();

Sayangnya, tipe yang dikembalikan tidak dapat memberi tahu Anda bahwa tipe generik arr adalah String. Ini perbedaan yang halus, tetapi penting. Karena arr dibuat saat runtime, tipe generik dihapus saat runtime sehingga Anda tidak dapat mengetahuinya. Seperti yang dinyatakan beberapa ArrayList<Integer>terlihat sama ArrayList<String>dari sudut pandang refleksi.

Ini mungkin tidak masalah bagi pengguna Generics, tetapi katakanlah kami ingin membuat beberapa kerangka kerja mewah yang menggunakan refleksi untuk mencari tahu hal-hal mewah tentang bagaimana pengguna menyatakan jenis generik konkret contoh.

Factory<MySpecialObject> factory = new Factory<MySpecialObject>();
MySpecialObject obj = factory.create();

Katakanlah kita menginginkan sebuah pabrik generik untuk membuat turunan MySpecialObjectkarena itulah jenis generik konkret yang kita nyatakan untuk turunan ini. Yah kelas Pabrik tidak dapat menginterogasi sendiri untuk mengetahui jenis beton yang dinyatakan untuk contoh ini karena Java menghapusnya.

Dalam. Net's generics Anda dapat melakukan ini karena saat runtime objek tahu itu tipe generik karena compiler mematuhinya ke dalam biner. Dengan penghapusan, Java tidak dapat melakukan ini.

chubbsondubs
sumber
1

Saya bisa mengatakan beberapa hal baik tentang obat generik, tetapi bukan itu pertanyaannya. Saya dapat mengeluh bahwa mereka tidak tersedia pada saat run time dan tidak bekerja dengan array, tetapi itu telah disebutkan.

Gangguan psikologis besar: Saya kadang-kadang masuk ke situasi di mana obat generik tidak dapat dibuat untuk bekerja. (Array menjadi contoh paling sederhana.) Dan saya tidak tahu apakah obat generik tidak dapat melakukan pekerjaan atau apakah saya hanya bodoh. Aku benci itu. Sesuatu seperti obat generik harus selalu bekerja. Setiap kali saya tidak bisa melakukan apa yang saya inginkan menggunakan bahasa Jawa, saya tahu masalahnya adalah saya, dan saya tahu jika saya terus mendorong, saya akan sampai di sana akhirnya. Dengan obat generik, jika saya menjadi terlalu gigih, saya bisa membuang banyak waktu.

Tetapi masalah sebenarnya adalah bahwa obat generik menambah kompleksitas terlalu banyak untuk keuntungan terlalu sedikit. Dalam kasus paling sederhana, saya bisa menghentikan menambahkan apel ke daftar yang berisi mobil. Baik. Tetapi tanpa obat generik kesalahan ini akan membuat ClassCastException benar-benar cepat pada saat dijalankan dengan sedikit waktu yang terbuang. Jika saya menambahkan mobil dengan kursi anak dengan anak di dalamnya, apakah saya perlu peringatan waktu kompilasi bahwa daftar ini hanya untuk mobil dengan kursi anak yang berisi simpanse bayi? Daftar instance Objek sederhana mulai terlihat seperti ide yang bagus.

Kode generik dapat memiliki banyak kata dan karakter dan membutuhkan banyak ruang ekstra dan membutuhkan lebih banyak waktu untuk membaca. Saya dapat menghabiskan banyak waktu untuk mendapatkan semua kode tambahan agar berfungsi dengan benar. Ketika itu terjadi, saya merasa sangat pintar. Saya juga telah menyia-nyiakan waktu saya sendiri beberapa jam atau lebih dan saya harus bertanya-tanya apakah ada orang lain yang bisa mengetahui kode tersebut. Saya ingin dapat menyerahkannya untuk pemeliharaan kepada orang yang kurang pandai daripada saya sendiri atau yang memiliki lebih sedikit waktu untuk dihabiskan.

Di sisi lain (saya mendapat sedikit luka di sana dan merasa perlu memberikan keseimbangan), itu bagus ketika menggunakan koleksi dan peta sederhana untuk menambah, menempatkan, dan diperiksa ketika ditulis, dan biasanya tidak menambah banyak kompleksitas ke kode ( jika orang lain menulis koleksi atau peta) . Dan Java lebih baik dalam hal ini daripada C #. Tampaknya tidak ada koleksi C # yang ingin saya gunakan yang pernah menangani obat generik. (Aku akui aku punya selera aneh dalam koleksi.)

RalphChapin
sumber
Kata-kata kasar yang bagus. Namun, ada banyak perpustakaan / kerangka kerja yang bisa ada tanpa obat generik, misalnya Guice, Gson, Hibernate. Dan obat generik tidak terlalu sulit setelah Anda terbiasa. Mengetik dan membaca argumen tipe adalah PITA nyata; di sini val membantu jika Anda dapat menggunakannya.
maaartinus
1
@maaartinus: Menulis itu beberapa waktu lalu. OP juga meminta "masalah". Saya menemukan obat generik sangat berguna dan sangat menyukainya - lebih dari sekarang. Namun, jika Anda menulis koleksi Anda sendiri, mereka sangat sulit dipelajari. Dan kegunaannya memudar ketika tipe koleksi Anda ditentukan pada saat run time - ketika koleksi memiliki metode yang memberitahu Anda kelas entri. Pada titik ini obat generik tidak berfungsi dan IDE Anda akan menghasilkan ratusan peringatan yang tidak berarti. 99,99% pemrograman Java tidak melibatkan ini. Tapi saya baru saja mengalami banyak masalah dengan ini ketika saya menulis di atas.
RalphChapin
Menjadi pengembang Java dan C #, saya bertanya-tanya mengapa Anda lebih suka koleksi Java?
Ivaylo Slavov
Fine. But without generics this error would throw a ClassCastException really quick at run time with little time wasted.Itu benar-benar tergantung pada berapa banyak waktu menjalankan terjadi antara startup program dan menekan garis kode yang dimaksud. Jika pengguna melaporkan kesalahan dan Anda perlu beberapa menit (atau, skenario terburuk, berjam-jam atau bahkan berhari-hari) untuk mereproduksi, verifikasi waktu kompilasi itu mulai terlihat lebih baik dan lebih baik ...
Mason Wheeler