Mengapa array kovarian tetapi obat generik tidak berubah-ubah?

160

From Java Efektif oleh Joshua Bloch,

  1. Array berbeda dari tipe generik dalam dua cara penting. Array pertama adalah kovarian. Obat generik tidak berubah-ubah.
  2. Kovarian berarti jika X adalah subtipe Y maka X [] juga akan menjadi sub tipe Y []. Array adalah kovarian Karena string adalah subtipe dari Objek Jadi

    String[] is subtype of Object[]

    Invarian berarti tidak peduli X menjadi subtipe Y atau tidak,

     List<X> will not be subType of List<Y>.

Pertanyaan saya adalah mengapa keputusan untuk membuat array kovarian di Jawa? Ada posting SO lainnya seperti Mengapa Array invarian, tetapi Daftar kovarian? , tetapi mereka tampaknya berfokus pada Scala dan saya tidak dapat mengikuti.

semangat belajar
sumber
1
Bukankah ini karena obat generik ditambahkan kemudian?
Sotirios Delimanolis
1
saya pikir membandingkan antara array dan koleksi tidak adil, Koleksi menggunakan array di latar belakang !!
Ahmed Adel Ismail
4
@ EL-conteDe-monteTereBentikh Tidak semua koleksi, misalnya LinkedList.
Paul Bellora
@ PaulBellora saya tahu bahwa Maps berbeda dari implementer Collection, tetapi saya membaca di SCPJ6 bahwa Koleksi umumnya mengandalkan array !!
Ahmed Adel Ismail
Karena tidak ada ArrayStoreException; ketika memasukkan elemen yang salah di Collection dimana array memilikinya. Jadi Collection dapat menemukan ini hanya pada waktu pengambilan dan itu juga karena casting. Jadi obat generik akan memastikan pemecahan masalah ini.
Kanagavelu Sugumar

Jawaban:

150

Melalui wikipedia :

Versi awal Java dan C # tidak termasuk obat generik (alias parametrik polimorfisme).

Dalam pengaturan seperti itu, membuat array invarian mengesampingkan program polimorfik yang berguna. Misalnya, pertimbangkan untuk menulis fungsi untuk mengocok array, atau fungsi yang menguji dua array untuk kesetaraan menggunakan Object.equalsmetode pada elemen. Implementasi tidak tergantung pada jenis elemen yang disimpan dalam array, jadi harus dimungkinkan untuk menulis fungsi tunggal yang bekerja pada semua jenis array. Sangat mudah untuk mengimplementasikan fungsi tipe

boolean equalArrays (Object[] a1, Object[] a2);
void shuffleArray(Object[] a);

Namun, jika tipe array diperlakukan sebagai invarian, hanya akan mungkin untuk memanggil fungsi-fungsi ini pada array dengan tipe yang tepat Object[]. Seseorang tidak bisa, misalnya, mengocok array string.

Oleh karena itu, Java dan C # memperlakukan tipe array secara kovarian. Misalnya, dalam C # string[]adalah subtipe dari object[], dan di Jawa String[]adalah subtipe dari Object[].

Ini menjawab pertanyaan "Mengapa array kovarian?", Atau lebih tepatnya, "Mengapa tidak array dibuat kovarian pada waktu itu ?"

Ketika obat generik diperkenalkan, mereka sengaja tidak dibuat kovarian karena alasan yang ditunjukkan dalam jawaban ini oleh Jon Skeet :

Tidak, a List<Dog>bukan a List<Animal>. Pertimbangkan apa yang dapat Anda lakukan dengan List<Animal>- Anda dapat menambahkan hewan apa pun ke dalamnya ... termasuk kucing. Sekarang, bisakah Anda secara logis menambahkan kucing ke sampah anak-anak anjing? Benar-benar tidak.

// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new List<Dog>();
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?

Tiba-tiba Anda memiliki kucing yang sangat bingung.

Motivasi asli untuk membuat array kovarian yang dijelaskan dalam artikel wikipedia tidak berlaku untuk obat generik karena wildcard memungkinkan ekspresi kovarians (dan contravariance) menjadi mungkin, misalnya:

boolean equalLists(List<?> l1, List<?> l2);
void shuffleList(List<?> l);
Paul Bellora
sumber
3
ya, Array memungkinkan untuk perilaku polimorfik, namun, ia memperkenalkan pengecualian runtime (tidak seperti pengecualian kompilasi dengan generik). misal:Object[] num = new Number[4]; num[1]= 5; num[2] = 5.0f; num[3]=43.4; System.out.println(Arrays.toString(num)); num[0]="hello";
eagertoPelajari
21
Itu benar. Array memiliki tipe yang dapat diverifikasi dan melempar ArrayStoreExceptionsesuai kebutuhan. Jelas ini dianggap kompromi yang layak pada saat itu. Bandingkan dengan hari ini: banyak yang menganggap kovarian array sebagai kesalahan, jika dipikir-pikir.
Paul Bellora
1
Mengapa "banyak" menganggapnya sebagai kesalahan? Ini jauh lebih bermanfaat daripada tidak memiliki array kovarians. Seberapa sering Anda melihat ArrayStoreException; mereka cukup langka. Ironi di sini adalah imo yang tak termaafkan ... di antara kesalahan terburuk yang pernah dibuat di Jawa adalah varians penggunaan-situs alias wildcard.
Scott
3
@ScottMcKinney: "Mengapa" banyak "menganggapnya sebagai kesalahan?" AIUI, ini karena array covariance memerlukan tes tipe dinamis pada semua operasi penugasan array (meskipun optimisasi kompiler mungkin dapat membantu?) Yang dapat menyebabkan overhead runtime yang signifikan.
Dominique Devriese
Terima kasih, Dominique, tetapi dari pengamatan saya, tampaknya alasan "banyak orang" menganggapnya sebagai kesalahan lebih sesuai dengan apa yang dikatakan oleh beberapa orang lain. Sekali lagi, melihat segar pada array kovarians, ini jauh lebih berguna daripada merusak. Sekali lagi, kesalahan BESAR yang sebenarnya dibuat Java adalah varians generik penggunaan situs via wildcard. Itu telah menyebabkan lebih banyak masalah daripada yang saya pikir "banyak" ingin saya akui.
Scott
30

Alasannya adalah bahwa setiap array tahu tipe elemennya selama runtime, sedangkan koleksi generik tidak karena penghapusan tipe.

Sebagai contoh:

String[] strings = new String[2];
Object[] objects = strings;  // valid, String[] is Object[]
objects[0] = 12; // error, would cause java.lang.ArrayStoreException: java.lang.Integer during runtime

Jika ini diizinkan dengan koleksi umum:

List<String> strings = new ArrayList<String>();
List<Object> objects = strings;  // let's say it is valid
objects.add(12);  // invalid, Integer should not be put into List<String> but there is no information during runtime to catch this

Tetapi ini akan menimbulkan masalah nanti ketika seseorang mencoba mengakses daftar:

String first = strings.get(0); // would cause ClassCastException, trying to assign 12 to String
Katona
sumber
Saya pikir jawaban Paul Bellora lebih tepat karena ia mengomentari MENGAPA Array adalah kovarian. Jika array dibuat invarian, maka tidak masalah. Anda akan memiliki tipe penghapusan dengan itu. Alasan utama untuk tipe properti Erasure adalah untuk kompatibilitas mundur yang benar?
eagertoPelajari
@ user2708477, ya, tipe erasure diperkenalkan karena kompatibilitas ke belakang. Dan ya, jawaban saya mencoba menjawab pertanyaan dalam judul, mengapa obat generik tidak berubah.
Katona
Fakta bahwa array mengetahui tipenya berarti bahwa sementara kovarians memungkinkan kode untuk meminta untuk menyimpan sesuatu ke dalam array yang tidak cocok - itu tidak berarti toko seperti itu akan diizinkan terjadi. Akibatnya, tingkat bahaya yang ditimbulkan oleh memiliki array menjadi kovarian jauh lebih kecil daripada jika mereka tidak tahu tipenya.
supercat
@ supercat, benar, yang ingin saya tunjukkan adalah bahwa untuk obat generik dengan penghapusan tipe, kovarians tidak dapat diimplementasikan dengan keamanan minimal pemeriksaan runtime
Katona
1
Saya pribadi berpikir jawaban ini memberikan penjelasan yang tepat tentang mengapa Array adalah kovarian ketika Koleksi tidak dapat dilakukan. Terima kasih!
asgs
22

Mungkin bantuan ini : -

Generik bukan kovarian

Array dalam bahasa Java adalah kovarian - yang berarti bahwa jika Integer memperluas Number (yang memang), maka tidak hanya Integer juga Number, tetapi Integer [] juga a Number[], dan Anda bebas untuk lulus atau menetapkan Integer[]di mana a Number[]dipanggil. (Lebih formal, jika Number adalah supertype dari Integer, maka Number[]adalah supertype dari Integer[].) Anda mungkin berpikir hal yang sama berlaku untuk tipe generik juga - itu List<Number>adalah supertype dari List<Integer>, dan bahwa Anda dapat melewati di List<Integer>mana a List<Number>diharapkan. Sayangnya, tidak berfungsi seperti itu.

Ternyata ada alasan bagus mengapa hal itu tidak bekerja seperti itu: Ini akan memecah tipe keselamatan yang seharusnya disediakan oleh generik. Bayangkan Anda bisa menetapkan a List<Integer>ke List<Number>. Kemudian kode berikut akan memungkinkan Anda untuk memasukkan sesuatu yang bukan bilangan bulat ke dalam List<Integer>:

List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415));

Karena ln adalah a List<Number>, menambahkan Float ke tampak legal. Tetapi jika dalam aliased dengan li, maka itu akan melanggar janji keselamatan-jenis yang tersirat dalam definisi li - bahwa itu adalah daftar bilangan bulat, itulah sebabnya tipe generik tidak bisa kovarian.

Rahul Tripathi
sumber
3
Untuk Array, Anda mendapatkan waktu ArrayStoreExceptionruntime.
Sotirios Delimanolis
4
pertanyaan saya adalah WHYapakah Array dibuat kovarian. seperti yang disebutkan Sotirios, dengan Array kita akan mendapatkan ArrayStoreException saat runtime, jika Array dibuat invarian, maka kita dapat mendeteksi kesalahan ini pada waktu kompilasi itu sendiri, benar?
eagertoPelajari
@eagertoLearn: Salah satu kelemahan semantik utama di Jawa adalah bahwa tidak ada sistem tipenya yang dapat membedakan "Array yang tidak memiliki apa-apa selain turunan dari Animal, yang tidak harus menerima barang yang diterima dari tempat lain" dari "Array yang tidak boleh mengandung apa pun kecuali Animal, dan harus bersedia menerima referensi yang disediakan secara eksternal untuk Animal. Kode yang membutuhkan yang pertama harus menerima array Cat, tetapi kode yang membutuhkan yang terakhir tidak boleh. Jika kompiler dapat membedakan kedua jenis, itu dapat menyediakan waktu kompilasi memeriksa. Sayangnya, satu-satunya hal yang membedakan mereka ...
supercat
... adalah apakah kode benar-benar mencoba untuk menyimpan sesuatu ke dalamnya, dan tidak ada cara untuk mengetahui hal itu sampai runtime.
supercat
3

Array adalah kovarian untuk setidaknya dua alasan:

  • Berguna untuk koleksi yang menyimpan informasi yang tidak akan pernah berubah menjadi kovarian. Agar koleksi T menjadi kovarian, toko pendukungnya juga harus kovarian. Sementara orang dapat merancang Tkoleksi abadi yang tidak menggunakan T[]sebagai backing store (mis. Menggunakan pohon atau daftar tertaut), koleksi seperti itu tidak akan berkinerja sebaik koleksi yang didukung oleh array. Orang mungkin berpendapat bahwa cara yang lebih baik untuk menyediakan koleksi yang tidak dapat diubah kovarian adalah dengan mendefinisikan tipe "array tidak berubah yang kovarian" yang bisa mereka gunakan di backing store, tetapi dengan membiarkan array kovarians mungkin lebih mudah.

  • Array akan sering dimutasi oleh kode yang tidak tahu apa yang akan ada di dalamnya, tetapi tidak akan memasukkan ke dalam array apa pun yang tidak dibaca dari array yang sama. Contoh utama dari ini adalah kode penyortiran. Secara konseptual, dimungkinkan untuk tipe array untuk memasukkan metode untuk bertukar atau mengubah elemen (metode seperti itu bisa sama-sama berlaku untuk semua tipe array), atau mendefinisikan objek "manipulator array" yang menyimpan referensi ke array dan satu atau lebih hal yang telah dibaca dari itu, dan dapat mencakup metode untuk menyimpan item yang sebelumnya dibaca ke dalam array dari mana mereka datang. Jika array bukan kovarian, kode pengguna tidak akan dapat mendefinisikan tipe seperti itu, tetapi runtime bisa menyertakan beberapa metode khusus.

Fakta bahwa array adalah kovarian dapat dipandang sebagai peretasan yang buruk, tetapi dalam kebanyakan kasus itu memfasilitasi pembuatan kode kerja.

supercat
sumber
1
The fact that arrays are covariant may be viewed as an ugly hack, but in most cases it facilitates the creation of working code.- poin bagus
eagertoPelajari
3

Fitur penting dari tipe parametrik adalah kemampuan untuk menulis algoritma polimorfik, yaitu algoritma yang beroperasi pada struktur data terlepas dari nilai parameternya, seperti Arrays.sort().

Dengan obat generik, itu dilakukan dengan tipe wildcard:

<E extends Comparable<E>> void sort(E[]);

Agar benar-benar bermanfaat, tipe wildcard memerlukan penangkapan wildcard, dan itu memerlukan gagasan tentang parameter tipe. Tidak ada yang tersedia pada saat array ditambahkan ke Jawa, dan membuat array tipe referensi kovarian memungkinkan cara yang jauh lebih sederhana untuk mengizinkan algoritma polimorfik:

void sort(Comparable[]);

Namun, kesederhanaan itu membuka celah pada sistem tipe statis:

String[] strings = {"hello"};
Object[] objects = strings;
objects[0] = 1; // throws ArrayStoreException

membutuhkan pemeriksaan runtime dari setiap akses tulis ke berbagai jenis referensi.

Singkatnya, pendekatan yang lebih baru yang diwujudkan oleh obat generik membuat sistem tipe lebih kompleks, tetapi juga tipe yang lebih aman, sedangkan pendekatan yang lebih lama lebih sederhana, dan tipe yang kurang aman secara statis. Para perancang bahasa memilih untuk pendekatan yang lebih sederhana, memiliki hal-hal yang lebih penting untuk dilakukan daripada menutup celah kecil dalam sistem tipe yang jarang menyebabkan masalah. Kemudian, ketika Java didirikan, dan kebutuhan mendesak diatasi, mereka memiliki sumber daya untuk melakukannya dengan benar untuk generik (tetapi mengubahnya untuk array akan merusak program Java yang ada).

meriton
sumber
2

Generik tidak berubah : dari JSL 4.10 :

... Subtipe tidak meluas melalui tipe generik: T <: U tidak menyiratkan bahwa C<T><: C<U>...

dan beberapa baris lebih jauh, JLS juga menjelaskan bahwa
Array adalah kovarian (peluru pertama):

4.10.3 Subtipe di antara Jenis Array

masukkan deskripsi gambar di sini

Alfasin
sumber
2

Saya pikir mereka membuat keputusan yang salah pada awalnya yang membuat array kovarian. Itu merusak keamanan jenis seperti yang dijelaskan di sini dan mereka terjebak dengan itu karena kompatibilitas ke belakang dan setelah itu mereka mencoba untuk tidak membuat kesalahan yang sama untuk generik. Dan itulah salah satu alasan mengapa Joshua Bloch lebih memilih daftar daripada di Item 25 buku "Java Efektif (edisi kedua)"

Arnold
sumber
Josh Block adalah penulis kerangka koleksi Java (1.2), dan penulis generik Java (1.5). Jadi pria yang membuat obat generik yang dikeluhkan semua orang juga kebetulan pria yang menulis buku itu mengatakan bahwa mereka adalah cara yang lebih baik untuk pergi? Bukan kejutan besar!
canggung,
1

Saya ambil: Ketika kode mengharapkan array A [] dan Anda memberinya B [] di mana B adalah subkelas A, hanya ada dua hal yang perlu dikhawatirkan: apa yang terjadi ketika Anda membaca elemen array, dan apa yang terjadi jika Anda menulis Itu. Jadi tidak sulit untuk menulis aturan bahasa untuk memastikan bahwa keamanan tipe dipertahankan dalam semua kasus (aturan utama adalah bahwa pelemparan huruf ArrayStoreExceptionbisa dilemparkan jika Anda mencoba memasukkan A ke dalam B []). Namun, untuk generik, ketika Anda mendeklarasikan sebuah kelas SomeClass<T>, mungkin ada sejumlah cara Tyang digunakan dalam tubuh kelas, dan saya menduga itu terlalu rumit untuk mengerjakan semua kombinasi yang mungkin untuk menulis aturan tentang kapan hal-hal diperbolehkan dan ketika tidak.

ajb
sumber