Apa manfaat dari penghapusan tipe Java?


98

Saya membaca tweet hari ini yang mengatakan:

Sangat lucu ketika pengguna Java mengeluh tentang penghapusan tipe, yang merupakan satu-satunya hal yang dilakukan Java dengan benar, sambil mengabaikan semua hal yang salah.

Jadi pertanyaan saya adalah:

Apakah ada manfaat dari penghapusan tipe Java? Apa keuntungan teknis atau gaya pemrograman yang (mungkin) tawarkan selain preferensi implementasi JVM untuk kompatibilitas mundur dan kinerja runtime?


6
Jika Anda tidak ingin Pertanyaan Anda ditutup sebagai "berdasarkan opini", Anda harus menanyakannya secara berbeda. (Seperti "apa keuntungan teknis dari penghapusan tipe"). Lebih baik lagi, tanyakan tweeter apa yang sebenarnya dia katakan.
Stephen C

3
Hapus "mengapa itu hal yang baik". "Hal yang baik" adalah karakterisasi yang sangat subjektif, dan (jelas) menimbulkan pertanyaan bahwa penghapusan jenis adalah hal yang baik ... yang kontroversial.
Stephen C

3
Tuhan tolong kami jika situs ini akan merosot menjadi pertanyaan tentang segala sesuatu yang pernah diposting di Twitter. Setidaknya kutip sumber terpercaya untuk pertanyaan Anda.
Marquis dari Lorne

21
Mengapa pertanyaan harus dari "sumber terpercaya"? Itu konyol.
vertti

4
Sebagian besar masalah di sini bukan dari sumber APAPUN, kecuali penanya sendiri memiliki masalah dengan perangkat lunak / bahasa / yang lain, dan untungnya ada banyak profesional yang "membuang" waktu mereka untuk membantu orang-orang ini.
vertti

Jawaban:


90

Jenis Penghapusan Baik

Mari berpegang pada fakta

Banyak jawaban yang sejauh ini terlalu berkaitan dengan pengguna Twitter. Sangat membantu untuk tetap fokus pada pesan dan bukan pada pembawa pesan. Ada pesan yang cukup konsisten bahkan hanya dengan kutipan yang disebutkan sejauh ini:

Sangat lucu ketika pengguna Java mengeluh tentang penghapusan tipe, yang merupakan satu-satunya hal yang dilakukan Java dengan benar, sambil mengabaikan semua hal yang salah.

Saya mendapatkan keuntungan besar (misalnya parametrikitas) dan biaya nihil (dugaan biaya adalah batas imajinasi).

T baru adalah program yang rusak. Adalah isomorfik untuk klaim "semua proposisi benar". Saya tidak terlalu suka ini.

Sasaran: program yang masuk akal

Tweet ini mencerminkan perspektif yang tidak tertarik pada apakah kita dapat membuat mesin melakukannya sesuatu , tetapi lebih pada apakah kita dapat beralasan bahwa mesin akan melakukan sesuatu yang sebenarnya kita inginkan. Penalaran yang baik adalah buktinya. Bukti dapat ditentukan dalam notasi formal atau sesuatu yang kurang formal. Terlepas dari bahasa spesifikasinya, mereka harus jelas dan ketat. Spesifikasi informal bukan tidak mungkin untuk disusun dengan benar, tetapi sering kali cacat dalam pemrograman praktis. Kami berakhir dengan remediasi seperti tes otomatis dan eksplorasi untuk mengatasi masalah yang kami hadapi dengan penalaran informal. Ini bukan untuk mengatakan bahwa pengujian pada dasarnya adalah ide yang buruk, tetapi pengguna Twitter yang dikutip menunjukkan bahwa ada cara yang jauh lebih baik.

Jadi, tujuan kami adalah memiliki program yang benar yang dapat kami pertimbangkan dengan jelas dan tepat dengan cara yang sesuai dengan cara mesin akan menjalankan program tersebut. Namun, ini bukan satu-satunya tujuan. Kami juga ingin logika kami memiliki tingkat ekspresi yang tinggi. Misalnya, hanya ada begitu banyak yang bisa kita ekspresikan dengan logika proposisional. Sangat menyenangkan memiliki penghitungan universal (∀) dan eksistensial (∃) dari sesuatu seperti logika orde pertama.

Menggunakan sistem tipe untuk penalaran

Sasaran ini dapat ditangani dengan sangat baik oleh sistem tipe. Ini sangat jelas karena korespondensi Curry-Howard . Korespondensi ini sering diungkapkan dengan analogi berikut: tipe adalah program sebagaimana teorema untuk bukti.

Korespondensi ini cukup mendalam. Kita dapat mengambil ekspresi logis, dan menerjemahkannya melalui korespondensi ke tipe. Kemudian jika kita memiliki program dengan tanda tangan tipe yang sama yang dikompilasi, kita telah membuktikan bahwa ekspresi logis adalah benar secara universal (tautologi). Ini karena korespondensinya dua arah. Transformasi antara tipe / program dan dunia teorema / pembuktian bersifat mekanis, dan dalam banyak kasus dapat diotomatiskan.

Curry-Howard berperan dengan baik pada apa yang ingin kami lakukan dengan spesifikasi untuk suatu program.

Apakah sistem tipe berguna di Java?

Bahkan dengan pemahaman tentang Curry-Howard, beberapa orang merasa mudah untuk mengabaikan nilai sistem tipe, ketika itu

  1. sangat sulit untuk diajak bekerja sama
  2. sesuai (melalui Curry-Howard) dengan logika dengan ekspresi terbatas
  3. rusak (yang mendapat karakterisasi sistem sebagai "lemah" atau "kuat").

Mengenai poin pertama, mungkin IDE membuat sistem tipe Java cukup mudah untuk digunakan (itu sangat subjektif).

Mengenai poin kedua, Java hampir sesuai dengan logika orde pertama. Generik menggunakan sistem tipe yang setara dengan kuantifikasi universal. Sayangnya, karakter pengganti hanya memberi kita sebagian kecil dari kuantifikasi eksistensial. Tapi penghitungan universal adalah awal yang cukup bagus. Sangat menyenangkan dapat mengatakan bahwa fungsi List<A>berfungsi secara universal untuk semua daftar yang memungkinkan karena A sama sekali tidak dibatasi. Ini mengarah pada apa yang dibicarakan pengguna Twitter sehubungan dengan "parametrikitas".

Makalah yang sering dikutip tentang parametrik adalah Teorema Philip Wadler gratis! . Yang menarik dari tulisan ini adalah hanya dari jenis tanda tangannya saja, kita bisa membuktikan beberapa invarian yang sangat menarik. Jika kita menulis pengujian otomatis untuk invarian ini, kita akan sangat membuang-buang waktu. Misalnya untuk List<A>, dari type signature saja forflatten

<A> List<A> flatten(List<List<A>> nestedLists);

kita bisa beralasan itu

flatten(nestedList.map(l -> l.map(any_function)))
     flatten(nestList).map(any_function)

Itu adalah contoh sederhana, dan Anda mungkin dapat bernalar tentangnya secara informal, tetapi akan lebih bagus lagi ketika kita mendapatkan bukti seperti itu secara resmi secara gratis dari sistem tipe dan diperiksa oleh kompiler.

Tidak menghapus dapat menyebabkan penyalahgunaan

Dari perspektif implementasi bahasa, generik Java (yang sesuai dengan tipe universal) sangat berperan dalam parametrikitas yang digunakan untuk mendapatkan bukti tentang apa yang dilakukan program kami. Ini sampai pada masalah ketiga yang disebutkan. Semua perolehan bukti dan ketepatan ini membutuhkan sistem tipe suara yang diimplementasikan tanpa cacat. Java pasti memiliki beberapa fitur bahasa yang memungkinkan kita menghancurkan penalaran kita. Ini termasuk tetapi tidak terbatas pada:

  • efek samping dengan sistem eksternal
  • refleksi

Obat generik yang tidak terhapus dalam banyak hal terkait dengan refleksi. Tanpa penghapusan ada informasi runtime yang dibawa dengan implementasi yang dapat kita gunakan untuk mendesain algoritma kita. Artinya, secara statis, ketika kita berpikir tentang program, kita tidak memiliki gambaran yang lengkap. Refleksi sangat mengancam kebenaran dari bukti apa pun yang kami pertimbangkan secara statis. Bukan kebetulan refleksi juga menyebabkan berbagai cacat rumit.

Jadi apa cara obat generik tak terhapus bisa "berguna?" Mari pertimbangkan penggunaan yang disebutkan dalam tweet:

<T> T broken { return new T(); }

Apa yang terjadi jika T tidak memiliki konstruktor no-arg? Dalam beberapa bahasa, apa yang Anda dapatkan adalah null. Atau mungkin Anda melewatkan nilai null dan langsung memunculkan pengecualian (yang tampaknya mengarah ke nilai null). Karena bahasa kami Turing lengkap, tidak mungkin untuk menentukan panggilan manabroken akan melibatkan tipe "aman" dengan konstruktor tanpa argumen dan mana yang tidak. Kami telah kehilangan kepastian bahwa program kami bekerja secara universal.

Menghapus berarti kita sudah beralasan (jadi mari kita hapus)

Jadi jika kami ingin bernalar tentang program kami, kami sangat disarankan untuk tidak menggunakan fitur bahasa yang sangat mengancam penalaran kami. Setelah kita melakukan itu, lalu mengapa tidak menjatuhkan tipe pada saat runtime? Mereka tidak dibutuhkan. Kita bisa mendapatkan efisiensi dan kesederhanaan dengan kepuasan bahwa tidak ada pemeran yang gagal atau metode mungkin hilang saat pemanggilan.

Menghapus mendorong penalaran.


7
Saat saya menggabungkan jawaban ini, beberapa orang lain menjawab dengan jawaban yang serupa. Tetapi setelah menulis sebanyak ini, saya memutuskan untuk mempostingnya daripada membuangnya.
Sukant Hajra

32
Sehubungan dengan itu, penghapusan tipe adalah upaya setengah-setengah untuk menghadirkan fitur ke Java tanpa merusak kompatibilitas ke belakang. Itu bukan kekuatan. Jika perhatian utamanya adalah Anda mungkin mencoba membuat instance objek tanpa konstruktor tanpa parameter, jawaban yang benar adalah mengizinkan batasan "tipe dengan konstruktor tanpa parameter", bukan untuk melumpuhkan fitur bahasa.
Dasar

5
Dasar, dengan rasa hormat, Anda tidak membuat argumen yang kohesif. Anda hanya menegaskan bahwa penghapusan "melumpuhkan" dengan mengabaikan nilai bukti waktu kompilasi sebagai alat yang menarik untuk menalar tentang program. Selain itu, manfaat ini sepenuhnya ortogonal dari jalur bengkok dan motivasi yang diambil Java untuk mengimplementasikan obat generik. Selain itu, manfaat ini berlaku untuk bahasa dengan implementasi penghapusan yang jauh lebih tercerahkan.
Sukant Hajra

44
Argumen yang disajikan di sini tidak menentang penghapusan, tetapi menentang refleksi (dan pemeriksaan jenis runtime secara umum) - pada dasarnya menegaskan bahwa refleksi itu buruk, jadi fakta bahwa penghapusan tidak berfungsi dengan baik adalah baik. Ini adalah poin yang valid dengan sendirinya (meskipun banyak orang akan tidak setuju); tetapi itu tidak masuk akal dalam konteks, karena Java sudah memiliki pemeriksaan refleksi / runtime, dan mereka tidak dan tidak akan pergi. Jadi, memiliki konstruksi yang tidak sesuai dengan bagian bahasa yang sudah ada dan tidak digunakan lagi adalah batasan, tidak peduli bagaimana Anda melihatnya.
Pavel Minaev

10
Jawaban Anda di luar topik. Anda memasukkan penjelasan bertele-tele (meskipun menarik dengan istilahnya sendiri) mengapa penghapusan jenis sebagai konsep abstrak umumnya berguna dalam desain sistem jenis, tetapi topiknya adalah manfaat dari karakteristik khusus Java Generik berlabel "penghapusan jenis ", yang hanya secara dangkal menyerupai konsep yang Anda gambarkan, gagal sepenuhnya dalam menyediakan invarian yang menarik dan memperkenalkan batasan yang tidak masuk akal.
Marko Topolnik

40

Tipe adalah konstruksi yang digunakan untuk menulis program dengan cara yang memungkinkan kompilator untuk memeriksa kebenaran program. Tipe adalah proposisi pada nilai - kompilator memverifikasi bahwa proposisi ini benar.

Selama menjalankan sebuah program, seharusnya tidak diperlukan informasi tipe - ini telah diverifikasi oleh kompilator. Kompilator harus bebas membuang informasi ini untuk melakukan pengoptimalan pada kode - membuatnya berjalan lebih cepat, menghasilkan biner yang lebih kecil, dll. Penghapusan parameter tipe memfasilitasi ini.

Java memecah pengetikan statis dengan mengizinkan informasi jenis ditanyakan saat runtime - refleksi, instance, dll. Hal ini memungkinkan Anda untuk membangun program yang tidak dapat diverifikasi secara statis - mereka melewati sistem jenis. Itu juga melewatkan peluang untuk pengoptimalan statis.

Fakta bahwa parameter tipe dihapus mencegah beberapa contoh program yang salah ini untuk dibangun, namun, program yang lebih salah akan dilarang jika informasi tipe lebih banyak dihapus dan refleksi serta fasilitas instans dihapus.

Penghapusan penting untuk mempertahankan properti "parametrik" tipe data. Katakanlah saya memiliki tipe "Daftar" yang diparameterisasi di atas komponen tipe T. yaitu Daftar <T>. Tipe itu adalah proposisi bahwa tipe Daftar ini bekerja secara identik untuk semua tipe T. Fakta bahwa T adalah parameter tipe abstrak dan tak terbatas berarti bahwa kita tidak tahu apa-apa tentang tipe ini, oleh karena itu dilarang melakukan sesuatu yang khusus untuk kasus khusus T.

misalnya mengatakan saya memiliki Daftar xs = asList ("3"). Saya menambahkan elemen: xs.add ("q"). Saya berakhir dengan ["3", "q"]. Karena ini parametrik, saya dapat mengasumsikan bahwa List xs = asList (7); xs.add (8) diakhiri dengan [7,8] Saya tahu dari tipe bahwa ia tidak melakukan satu hal untuk String dan satu hal untuk Int.

Lebih jauh, saya tahu bahwa fungsi List.add tidak dapat menemukan nilai T dari udara tipis. Saya tahu bahwa jika asList saya ("3") memiliki "7" yang ditambahkan ke dalamnya, satu-satunya jawaban yang mungkin akan dibuat dari nilai "3" dan "7". Tidak ada kemungkinan "2" atau "z" ditambahkan ke daftar karena fungsi tidak dapat membangunnya. Tak satu pun dari nilai-nilai lain ini akan masuk akal untuk ditambahkan, dan parametrikitas mencegah program yang salah ini dibangun.

Pada dasarnya, penghapusan mencegah beberapa cara melanggar parametrik, sehingga menghilangkan kemungkinan program yang salah, yang merupakan tujuan pengetikan statis.


15

(Meskipun saya sudah menulis jawaban di sini, meninjau kembali pertanyaan ini dua tahun kemudian saya menyadari ada cara lain yang sama sekali berbeda untuk menjawabnya, jadi saya membiarkan jawaban sebelumnya tetap utuh dan menambahkan yang ini.)


Sangat diperdebatkan apakah proses yang dilakukan pada Java Generics layak diberi nama "penghapusan tipe". Karena tipe generik tidak dihapus tetapi diganti dengan tipe mentahnya, pilihan yang lebih baik sepertinya adalah "tipe mutilasi".

Fitur klasik dari penghapusan tipe dalam pengertian yang umum dipahami memaksa runtime untuk tetap berada dalam batas-batas sistem tipe statis dengan membuatnya "buta" terhadap struktur data yang diaksesnya. Ini memberikan kekuatan penuh kepada kompilator dan memungkinkannya untuk membuktikan teorema berdasarkan tipe statis saja. Ini juga membantu programmer dengan membatasi derajat kebebasan kode, memberikan lebih banyak kekuatan untuk penalaran sederhana.

Penghapusan jenis Java tidak mencapai itu — ia melumpuhkan kompilator, seperti dalam contoh ini:

void doStuff(List<Integer> collection) { 
}

void doStuff(List<String> collection) // ERROR: a method cannot have 
                   // overloads which only differ in type parameters

(Dua deklarasi di atas diciutkan ke dalam tanda tangan metode yang sama setelah penghapusan.)

Di sisi lain, runtime masih dapat memeriksa jenis objek dan alasannya, tetapi karena wawasannya tentang jenis sebenarnya terhambat oleh penghapusan, pelanggaran jenis statis mudah dicapai dan sulit dicegah.

Untuk membuat segalanya lebih berbelit-belit, tanda tangan tipe asli dan yang dihapus hidup berdampingan dan dianggap paralel selama kompilasi. Ini karena seluruh proses bukan tentang menghapus informasi jenis dari runtime, tetapi tentang menyatukan sistem jenis generik ke dalam sistem jenis mentah lama untuk mempertahankan kompatibilitas ke belakang. Permata ini adalah contoh klasik:

public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)

(Redundan extends Objectharus ditambahkan untuk menjaga kompatibilitas mundur dari tanda tangan yang dihapus.)

Sekarang, dengan mengingat hal itu, mari kita lihat kembali kutipannya:

Lucu sekali ketika pengguna Java mengeluh tentang penghapusan tipe, yang merupakan satu-satunya hal yang Java benar

Apa sebenarnya yang dilakukan Java dengan benar? Apakah itu kata itu sendiri, apa pun artinya? Untuk kontras, lihat intjenis yang sederhana : tidak ada pemeriksaan jenis runtime yang pernah dilakukan, atau bahkan mungkin, dan eksekusi selalu aman untuk jenis yang sempurna. Yang ini apa penghapusan jenis terlihat seperti bila dilakukan dengan benar: Anda bahkan tidak tahu itu ada.


10

Satu hal yang saya tidak lihat dipertimbangkan di sini sama sekali adalah bahwa polimorfisme runtime OOP pada dasarnya bergantung pada reifikasi tipe pada saat runtime. Ketika sebuah bahasa yang tulang punggungnya dipegang oleh tipe refied memperkenalkan perluasan besar pada sistem tipenya dan mendasarkannya pada penghapusan tipe, disonansi kognitif adalah hasil yang tak terelakkan. Inilah yang terjadi pada komunitas Jawa; itulah mengapa penghapusan tipe telah menarik begitu banyak kontroversi, dan pada akhirnya mengapa ada rencana untuk membatalkannya di rilis Java mendatang . Menemukan sesuatu yang lucu dalam keluhan pengguna Java itu menunjukkan kesalahpahaman yang jujur ​​tentang semangat Jawa, atau lelucon yang secara sadar mencela.

Klaim "penghapusan adalah satu-satunya hal yang Java benar" menyiratkan klaim bahwa "semua bahasa yang didasarkan pada pengiriman dinamis terhadap jenis runtime argumen fungsi pada dasarnya cacat". Meskipun tentu saja merupakan klaim yang sah dengan sendirinya, dan yang bahkan dapat dianggap sebagai kritik yang valid untuk semua bahasa OOP termasuk Java, ia tidak dapat menempatkan dirinya sebagai titik penting untuk mengevaluasi dan mengkritik fitur dalam konteks Java , di mana runtime polimorfisme bersifat aksiomatik.

Singkatnya, sementara seseorang dapat secara valid menyatakan "penghapusan jenis adalah cara untuk masuk dalam desain bahasa", posisi yang mendukung penghapusan jenis dalam Java salah tempat hanya karena sudah sangat, sangat terlambat untuk itu dan telah terjadi bahkan pada saat bersejarah ketika Oak dipeluk oleh Sun dan diganti namanya menjadi Java.



Mengenai apakah pengetikan statis itu sendiri adalah arah yang tepat dalam desain bahasa pemrograman, ini cocok dengan konteks filosofis yang lebih luas dari apa yang menurut kami merupakan aktivitas pemrograman . Satu aliran pemikiran, yang secara jelas berasal dari tradisi klasik matematika, melihat program sebagai contoh dari satu konsep matematika atau lainnya (proposisi, fungsi, dll.), Tetapi terdapat kelas pendekatan yang sama sekali berbeda, yang melihat pemrograman sebagai cara untuk berbicara dengan mesin dan menjelaskan apa yang kita inginkan darinya. Dalam pandangan ini, program adalah entitas yang dinamis dan tumbuh secara organik, kebalikan dramatis dari aedifice yang didirikan dengan hati-hati dari program yang diketik secara statis.

Tampaknya wajar untuk menganggap bahasa dinamis sebagai langkah ke arah itu: konsistensi program muncul dari bawah ke atas, tanpa batasan apriori yang akan memaksakannya secara top-down. Paradigma ini dapat dilihat sebagai langkah menuju pemodelan proses dimana kita, manusia, menjadi apa adanya melalui pengembangan dan pembelajaran.


Terima kasih untuk itu. Ini adalah jenis pemahaman yang lebih dalam tentang filosofi yang terlibat yang saya harapkan untuk melihat pertanyaan ini.
Daniel Langdon

Pengiriman dinamis tidak memiliki ketergantungan apa pun pada tipe yang direifikasi. Seringkali bergantung pada apa yang oleh literatur pemrograman disebut "tag", tetapi meskipun demikian, tidak perlu menggunakan tag. Jika Anda membuat antarmuka (untuk sebuah objek), dan membuat contoh anonim dari antarmuka itu, maka Anda melakukan pengiriman dinamis tanpa memerlukan tipe yang direifikasi sama sekali.
Sukant Hajra

@ukanthajra Itu hanya membelah rambut. Tag adalah informasi waktu proses yang Anda perlukan untuk menyelesaikan jenis perilaku yang diperlihatkan instance. Ini berada di luar jangkauan sistem tipe statis, dan itulah inti dari konsep yang sedang dibahas.
Marko Topolnik

Ada tipe yang sangat statis yang dirancang untuk alasan ini yang disebut "tipe jumlah". Ini disebut juga "tipe varian" (dalam buku Jenis dan Bahasa Pemrograman yang sangat bagus dari Benjamin Pierce). Jadi ini sama sekali bukan membelah rambut. Selain itu, Anda dapat menggunakan katamorfisme / lipatan / pengunjung untuk pendekatan yang sepenuhnya tanpa tag.
Sukant Hajra

Terima kasih untuk pelajaran teori, tapi saya tidak melihat relevansinya. Topik kami adalah sistem tipe Java.
Marko Topolnik

9

Postingan berikutnya dari pengguna yang sama dalam percakapan yang sama:

T baru adalah program yang rusak. Adalah isomorfik untuk klaim "semua proposisi benar". Saya tidak terlalu suka ini.

(Ini adalah tanggapan atas pernyataan oleh pengguna lain, yaitu bahwa "tampaknya dalam beberapa situasi 'T baru' akan lebih baik", idenya new T()adalah tidak mungkin karena penghapusan jenis. (Ini masih bisa diperdebatkan - bahkan jika Ttersedia di runtime, bisa jadi kelas atau antarmuka abstrak, atau bisa juga Void, atau bisa jadi tidak ada konstruktor no-arg, atau konstruktor no-arg-nya bisa jadi privat (misalnya, karena itu seharusnya kelas tunggal), atau konstruktor no-arg dapat menentukan pengecualian yang dicentang yang tidak ditangkap atau ditentukan oleh metode generik - tetapi itulah premisnya. Terlepas dari itu, memang benar bahwa tanpa penghapusan Anda setidaknya dapat menulis T.class.newInstance(), yang menangani masalah tersebut.))

Pandangan ini, bahwa tipe isomorfik terhadap proposisi, menunjukkan bahwa pengguna memiliki latar belakang dalam teori tipe formal. (S) dia sangat mungkin tidak menyukai "tipe dinamis" atau "tipe runtime" dan lebih memilih Java tanpa downcast instanceofdan refleksi dan seterusnya. (Bayangkan bahasa seperti ML Standar, yang memiliki sistem jenis yang sangat kaya (statis) dan yang semantik dinamisnya tidak bergantung pada jenis informasi apa pun.)

Omong-omong, perlu diingat bahwa pengguna sedang melakukan trolling: meskipun dia cenderung dengan tulus lebih memilih bahasa yang diketik (secara statis), dia tidak dengan tulus mencoba meyakinkan orang lain tentang pandangan tersebut. Sebaliknya, tujuan utama dari tweet asli adalah untuk mengejek mereka yang tidak setuju, dan setelah beberapa dari mereka yang tidak setuju menimpali, pengguna memposting tweet lanjutan seperti "alasan java memiliki penghapusan tipe adalah karena Wadler dkk tahu apa yang mereka lakukan, tidak seperti pengguna java ". Sayangnya, hal ini membuat sulit untuk mengetahui apa yang sebenarnya dia pikirkan; tetapi untungnya, itu juga mungkin berarti bahwa itu tidak terlalu penting untuk dilakukan. Orang-orang dengan kedalaman sebenarnya pada pandangan mereka biasanya tidak menggunakan troll yang cukup bebas konten ini.


2
Itu tidak dapat dikompilasi di Jawa karena memiliki jenis penghapusan
Mark Rotteveel

1
@ MarkRotteveel: Terima kasih; Saya telah menambahkan paragraf untuk menjelaskan (dan mengomentari) itu.
ruakh

1
Dilihat dari campuran suara positif dan suara negatif, ini adalah jawaban paling kontroversial yang pernah saya posting. Anehnya aku bangga.
ruakh

6

Satu hal yang baik adalah tidak perlu mengubah JVM saat obat generik diperkenalkan. Java mengimplementasikan generik hanya pada level compiler.


1
JVM berubah dibandingkan dengan apa? Template juga tidak memerlukan perubahan JVM.
Marquis dari Lorne

5

Alasan penghapusan jenis adalah hal yang baik adalah karena hal-hal yang tidak memungkinkan itu berbahaya. Mencegah pemeriksaan argumen tipe pada waktu proses membuat pemahaman dan penalaran tentang program lebih mudah.

Pengamatan yang saya temukan agak kontra-intuitif adalah bahwa ketika tanda tangan fungsi lebih umum, tanda tangan fungsi menjadi lebih mudah dipahami. Ini karena jumlah implementasi yang mungkin berkurang. Pertimbangkan metode dengan tanda tangan ini, yang entah bagaimana kita tahu tidak memiliki efek samping:

public List<Integer> XXX(final List<Integer> l);

Apa kemungkinan implementasi dari fungsi ini? Sangat banyak. Anda hanya dapat mengetahui sedikit tentang fungsi ini. Ini bisa membalik daftar masukan. Itu bisa memasangkan int bersama-sama, menjumlahkannya dan mengembalikan daftar setengah ukuran. Ada banyak kemungkinan lain yang bisa dibayangkan. Sekarang pertimbangkan:

public <T> List<T> XXX(final List<T> l);

Ada berapa implementasi fungsi ini? Karena implementasi tidak dapat mengetahui jenis elemen, sejumlah besar implementasi sekarang dapat dikecualikan: elemen tidak dapat digabungkan, atau ditambahkan ke daftar atau disaring, dkk. Kami dibatasi pada hal-hal seperti: identitas (tidak ada perubahan pada daftar), menghapus elemen, atau membalik daftar. Fungsi ini lebih mudah dipikirkan berdasarkan tanda tangannya saja.

Kecuali… di Jawa Anda selalu bisa menipu sistem tipe. Karena penerapan metode umum tersebut dapat menggunakan hal-hal seperti instanceofcek dan / atau gips ke jenis arbitrer, penalaran kita berdasarkan tanda tangan jenis dapat dengan mudah dianggap tidak berguna. Fungsi tersebut dapat memeriksa jenis elemen dan melakukan sejumlah hal berdasarkan hasilnya. Jika peretasan waktu proses ini diizinkan, tanda tangan metode berparameterisasi menjadi kurang berguna bagi kami.

Jika Java tidak memiliki penghapusan tipe (yaitu, argumen tipe direifikasi saat runtime) maka ini hanya akan memungkinkan lebih banyak kejahatan yang merusak penalaran semacam ini. Dalam contoh di atas, implementasi hanya dapat melanggar ekspektasi yang ditetapkan oleh tanda tangan tipe jika daftar tersebut memiliki setidaknya satu elemen; tetapi jika Tdireifikasi, ia dapat melakukannya meskipun daftarnya kosong. Tipe yang direifikasi hanya akan meningkatkan kemungkinan (sudah sangat banyak) untuk menghalangi pemahaman kita tentang kode.

Penghapusan jenis membuat bahasa menjadi kurang "kuat". Tetapi beberapa bentuk "kekuatan" sebenarnya berbahaya.


1
Sepertinya Anda berpendapat bahwa obat generik terlalu rumit untuk Java devs untuk "dipahami dan dibicarakan", atau bahwa obat generik tidak dapat dipercaya untuk tidak mengambil risiko jika diberi kesempatan. Yang terakhir tampaknya selaras dengan etos Java (tanpa tipe yang tidak ditandatangani, dll) tetapi tampaknya sedikit merendahkan para pengembang
Dasar

1
@Basic Saya pasti telah mengekspresikan diri saya dengan sangat buruk, karena bukan itu yang saya maksudkan sama sekali. Masalahnya bukan karena obat generik itu rumit, tetapi hal-hal seperti mentransmisikan dan instanceofmenghalangi kemampuan kita untuk bernalar tentang apa yang dilakukan kode berdasarkan jenis. Jika Java harus mereifikasi argumen tipe, itu hanya akan membuat masalah ini menjadi lebih buruk. Menghapus tipe pada waktu proses memiliki efek membuat sistem tipe lebih berguna.
Lachlan

@lachlan itu sangat masuk akal bagi saya, dan menurut saya pembacaan normal itu tidak akan menimbulkan penghinaan bagi pengembang Java.
Rob Grant

3

Ini bukan jawaban langsung (OP ditanya "apa manfaatnya", saya jawab "apa kontra")

Dibandingkan dengan sistem tipe C #, penghapusan tipe Java sangat merepotkan untuk dua serangan

Anda tidak dapat mengimplementasikan sebuah antarmuka dua kali

Dalam C # Anda dapat mengimplementasikan keduanya IEnumerable<T1>dan IEnumerable<T2>dengan aman, terutama jika kedua tipe tidak berbagi satu leluhur yang sama (yaitu leluhur mereka adalah Object ).

Contoh praktis: di Spring Framework, Anda tidak dapat mengimplementasikan ApplicationListener<? extends ApplicationEvent>beberapa kali. Jika Anda membutuhkan perilaku yang berbeda berdasarkan TAnda perlu mengujiinstanceof

Anda tidak dapat melakukan T baru ()

(dan Anda memerlukan referensi ke Kelas untuk melakukan itu)

Seperti yang dikomentari orang lain, melakukan padanan dengan new T()hanya dapat dilakukan melalui refleksi, hanya dengan memanggil sebuah instance Class<T>, memastikan tentang parameter yang dibutuhkan oleh konstruktor. C # memungkinkan Anda melakukan new T() hanya jika Anda membatasi Tke konstruktor tanpa parameter. Jika Ttidak menghormati batasan itu, kesalahan kompilasi akan muncul.

Di Java, Anda akan sering dipaksa untuk menulis metode yang terlihat seperti berikut ini

public <T> T create(....params, Class<T> classOfT)
{

    ... whatever you do
    ... you will end up
    T = classOfT.newInstance();


    ... or more advanced reflection
    Constructor<T> parameterizedConstructorThatYouKnowAbout = classOfT.getConstructor(...,...);
}

Kekurangan pada kode di atas adalah:

  • Class.newInstance hanya berfungsi dengan konstruktor tanpa parameter. Jika tidak ada, ReflectiveOperationExceptionakan dilempar saat runtime
  • Konstruktor yang dipantulkan tidak menyoroti masalah pada waktu kompilasi seperti di atas. Jika Anda melakukan refaktorisasi, dari Anda bertukar argumen, Anda hanya akan tahu pada waktu proses

Jika saya adalah penulis C #, saya akan memperkenalkan kemampuan untuk menentukan satu atau lebih batasan konstruktor yang mudah diverifikasi pada waktu kompilasi (jadi saya dapat meminta misalnya konstruktor dengan string,stringparams). Tapi yang terakhir adalah spekulasi


2

Poin tambahan yang tampaknya tidak dipertimbangkan oleh jawaban lain: jika Anda benar-benar membutuhkan obat generik dengan pengetikan run-time, Anda dapat menerapkannya sendiri seperti ini:

public class GenericClass<T>
{
     private Class<T> targetClass;
     public GenericClass(Class<T> targetClass)
     {
          this.targetClass = targetClass;
     }

Kelas ini kemudian dapat melakukan semua hal yang dapat dicapai secara default jika Java tidak menggunakan penghapusan: ia dapat mengalokasikan Ts baru (dengan asumsi Tmemiliki konstruktor yang cocok dengan pola yang diharapkan untuk digunakan), atau array Ts, ia dapat menguji secara dinamis pada waktu proses jika objek tertentu adalah aT dan mengubah perilaku bergantung padanya, dan seterusnya.

Sebagai contoh:

     public T newT () { 
         try {
             return targetClass.newInstance(); 
         } catch(/* I forget which exceptions can be thrown here */) { ... }
     }

     private T value;
     /** @throws ClassCastException if object is not a T */
     public void setValueFromObject (Object object) {
         value = targetClass.cast(object);
     }
}

Adakah yang peduli untuk menjelaskan alasan downvote tersebut? Sejauh yang saya bisa lihat, ini adalah penjelasan yang sangat bagus tentang mengapa sisi negatif dari sistem penghapusan tipe Java sebenarnya bukan batasan yang nyata sama sekali. Jadi apa masalahnya?
Jules

Saya upvoting. Bukannya saya mendukung pemeriksaan jenis run-time, tetapi trik ini bisa berguna di tambang garam.
techtangents

3
Java adalah bahasa tambang garam, yang membuatnya menjadi kebutuhan utama mengingat kurangnya informasi jenis runtime. Mudah-mudahan, penghapusan tipe akan dibatalkan di versi Java yang akan datang, membuat generik menjadi fitur yang jauh lebih berguna. Saat ini konsensusnya adalah bahwa rasio dorong / berat mereka di bawah 1.
Marko Topolnik

Ya, tentu ... Selama TargetType Anda tidak menggunakan obat generik itu sendiri. Tapi itu sepertinya cukup membatasi.
Dasar

Ia bekerja dengan tipe generik juga. Satu-satunya kendala adalah jika Anda ingin membuat instance, Anda memerlukan konstruktor dengan tipe argumen yang tepat, tetapi setiap bahasa lain yang saya ketahui memiliki batasan yang sama, jadi saya tidak melihat batasannya.
Jules

0

menghindari c ++ - seperti kode mengasapi karena kode yang sama digunakan untuk beberapa tipe; Namun, penghapusan tipe memerlukan pengiriman virtual sedangkan pendekatan c ++ - code-bloat dapat melakukan pengiriman generik yang tidak dikirim secara virtual


3
Sebagian besar dari pengiriman virtual tersebut diubah menjadi yang statis dalam runtime, jadi tidak seburuk yang terlihat.
Piotr Kołaczkowski

1
sangat benar, ketersediaan compiler pada saat run-time memungkinkan java melakukan banyak hal keren (dan mencapai performa tinggi) relatif terhadap c ++.
Ahli nujum

wow, izinkan saya menuliskannya di suatu tempat di java mencapai kinerja tinggi relatif terhadap cpp ..
jungle_mole

1
@jungle_mole ya terima kasih. hanya sedikit orang yang menyadari manfaat memiliki kompiler yang tersedia pada waktu proses untuk mengompilasi kode lagi setelah pembuatan profil. (atau bahwa pengumpulan sampah itu besar-oh (bukan sampah) daripada kepercayaan yang naif dan salah bahwa pengumpulan sampah itu besar-oh (sampah), (atau bahwa meskipun pengumpulan sampah adalah sedikit usaha, itu mengimbanginya dengan membiarkan biaya nol alokasi)). Ini adalah dunia yang menyedihkan di mana orang-orang secara membabi buta mengadopsi apa yang diyakini komunitas mereka dan berhenti berpikir dari prinsip-prinsip pertama.
ahli nujum

0

Sebagian besar jawaban lebih memperhatikan filosofi pemrograman daripada detail teknis yang sebenarnya.

Dan meskipun pertanyaan ini berusia lebih dari 5 tahun, pertanyaannya masih tetap ada: Mengapa penghapusan jenis diinginkan dari sudut pandang teknis? Pada akhirnya, jawabannya cukup sederhana (pada level yang lebih tinggi): https://en.wikipedia.org/wiki/Type_erasure

Template C ++ tidak ada saat runtime. Kompilator mengeluarkan versi yang dioptimalkan sepenuhnya untuk setiap pemanggilan, yang berarti eksekusi tidak bergantung pada informasi jenis. Tapi bagaimana JIT menangani versi berbeda dari fungsi yang sama? Bukankah lebih baik hanya memiliki satu fungsi? Tidak ingin JIT harus mengoptimalkan semua versi yang berbeda darinya. Nah, lalu bagaimana dengan keamanan tipe? Tebak itu harus keluar dari jendela.

Tapi tunggu sebentar: Bagaimana .NET melakukannya? Refleksi! Dengan cara ini mereka hanya perlu mengoptimalkan satu fungsi dan juga mendapatkan informasi jenis runtime. Dan itulah mengapa .NET generik dulunya lebih lambat (meskipun menjadi jauh lebih baik). Saya tidak berpendapat bahwa itu tidak nyaman! Tetapi itu mahal dan tidak boleh digunakan ketika itu tidak mutlak diperlukan (itu tidak dianggap mahal dalam bahasa yang diketik secara dinamis karena kompiler / interpreter tetap bergantung pada refleksi).

Dengan cara ini pemrograman generik dengan penghapusan tipe mendekati nol overhead (beberapa pemeriksaan / gips runtime masih diperlukan): https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.