Yang pertama mengatakan bahwa itu "beberapa jenis yang merupakan leluhur E"; yang kedua mengatakan bahwa "beberapa jenis yang merupakan subkelas E". (Dalam kedua kasus E itu sendiri tidak masalah.)
Jadi konstruktor menggunakan ? extends E
formulir sehingga menjamin bahwa ketika mengambil nilai dari koleksi, mereka semua akan menjadi E atau beberapa subkelas (yaitu kompatibel). The drainTo
Metode berusaha untuk menempatkan nilai-nilai ke dalam koleksi, sehingga koleksi harus memiliki jenis unsur E
atau superclass .
Sebagai contoh, misalkan Anda memiliki hierarki kelas seperti ini:
Parent extends Object
Child extends Parent
dan a LinkedBlockingQueue<Parent>
. Anda dapat membuat passing ini dalam List<Child>
yang akan menyalin semua elemen dengan aman, karena setiap Child
orang tua. Anda tidak dapat memasukkan a List<Object>
karena beberapa elemen mungkin tidak kompatibel Parent
.
Demikian juga Anda dapat menguras antrian itu menjadi List<Object>
karena setiap Parent
adalah Object
... tetapi Anda tidak dapat mengalirkannya ke dalam List<Child>
karena List<Child>
mengharapkan semua elemennya kompatibel dengan Child
.
? extends InputStream
atau? super InputStream
kemudian Anda dapat menggunakanInputStream
argumen.Alasan untuk ini didasarkan pada bagaimana Java mengimplementasikan obat generik.
Contoh Array
Dengan array, Anda dapat melakukan ini (array bersifat kovarian)
Tetapi, apa yang akan terjadi jika Anda mencoba melakukan ini?
Baris terakhir ini akan mengkompilasi dengan baik, tetapi jika Anda menjalankan kode ini, Anda bisa mendapatkan
ArrayStoreException
. Karena Anda mencoba memasukkan dobel ke dalam array integer (terlepas dari diakses melalui referensi nomor).Ini berarti bahwa Anda dapat menipu kompilator, tetapi Anda tidak dapat menipu sistem tipe runtime. Dan ini terjadi karena array adalah apa yang kita sebut tipe yang dapat diverifikasi . Ini berarti bahwa pada saat runtime Java tahu bahwa array ini sebenarnya dipakai sebagai array integer yang kebetulan diakses melalui referensi tipe
Number[]
.Jadi, seperti yang Anda lihat, satu hal adalah jenis objek yang sebenarnya, dan satu hal lagi adalah jenis referensi yang Anda gunakan untuk mengaksesnya, bukan?
Masalah dengan Java Generics
Sekarang, masalah dengan tipe generik Java adalah bahwa informasi tipe dibuang oleh kompiler dan tidak tersedia pada saat run time. Proses ini disebut tipe erasure . Ada alasan bagus untuk mengimplementasikan obat generik seperti ini di Jawa, tetapi itu adalah cerita yang panjang, dan itu harus dilakukan, antara lain, dengan kompatibilitas biner dengan kode yang sudah ada sebelumnya (lihat Bagaimana kami mendapatkan obat generik yang kami miliki ).
Tetapi poin penting di sini adalah karena, pada saat runtime tidak ada informasi jenis, tidak ada cara untuk memastikan bahwa kami tidak melakukan polusi timbunan.
Misalnya,
Jika kompiler Java tidak menghentikan Anda dari melakukan ini, sistem tipe runtime tidak dapat menghentikan Anda juga, karena tidak ada cara, pada saat runtime, untuk menentukan bahwa daftar ini seharusnya menjadi daftar bilangan bulat saja. Java runtime akan membiarkan Anda memasukkan apa pun yang Anda inginkan ke dalam daftar ini, padahal seharusnya hanya berisi bilangan bulat, karena ketika dibuat, itu dinyatakan sebagai daftar bilangan bulat.
Dengan demikian, para perancang Java memastikan bahwa Anda tidak dapat membodohi kompiler. Jika Anda tidak dapat membodohi kompiler (seperti yang dapat kita lakukan dengan array), Anda juga tidak dapat mengelabui sistem tipe runtime.
Karena itu, kami mengatakan bahwa tipe generik tidak dapat diverifikasi .
Jelas, ini akan menghambat polimorfisme. Perhatikan contoh berikut:
Sekarang Anda bisa menggunakannya seperti ini:
Tetapi jika Anda mencoba menerapkan kode yang sama dengan koleksi umum, Anda tidak akan berhasil:
Anda akan mendapatkan eril kompiler jika Anda mencoba ...
Solusinya adalah belajar menggunakan dua fitur kuat dari generik Java yang dikenal sebagai kovarians dan contravariance.
Kovarian
Dengan kovarian Anda dapat membaca item dari suatu struktur, tetapi Anda tidak dapat menulis apa pun di dalamnya. Semua ini adalah deklarasi yang valid.
Dan Anda dapat membaca dari
myNums
:Karena Anda dapat yakin bahwa apa pun yang ada dalam daftar aktual, daftar itu dapat disebarluaskan ke suatu Angka (setelah semua hal yang meluas Angka adalah suatu Angka, bukan?)
Namun, Anda tidak diizinkan memasukkan apa pun ke dalam struktur kovarian.
Ini tidak akan diizinkan, karena Java tidak dapat menjamin apa jenis objek yang sebenarnya dalam struktur generik. Itu bisa berupa apa saja yang meluas Bilangan, tetapi kompiler tidak dapat memastikan. Jadi Anda bisa membaca, tetapi tidak menulis.
Contravariance
Dengan contravariance Anda bisa melakukan yang sebaliknya. Anda dapat meletakkan berbagai hal dalam struktur umum, tetapi Anda tidak dapat membacanya.
Dalam hal ini, sifat sebenarnya dari objek adalah Daftar Objek, dan melalui contravariance, Anda dapat memasukkan Angka ke dalamnya, pada dasarnya karena semua angka memiliki Objek sebagai leluhur bersama mereka. Dengan demikian, semua Bilangan adalah objek, dan karenanya ini valid.
Namun, Anda tidak dapat dengan aman membaca apa pun dari struktur contravarian ini dengan asumsi Anda akan mendapatkan nomor.
Seperti yang Anda lihat, jika kompiler mengizinkan Anda untuk menulis baris ini, Anda akan mendapatkan ClassCastException saat runtime.
Prinsip Get / Put
Dengan demikian, gunakan kovarians ketika Anda hanya bermaksud untuk mengambil nilai-nilai generik dari suatu struktur, gunakan contravariance ketika Anda hanya bermaksud untuk memasukkan nilai-nilai generik ke dalam struktur dan menggunakan tipe generik yang tepat ketika Anda ingin melakukan keduanya.
Contoh terbaik yang saya miliki adalah yang berikut ini yang menyalin segala jenis angka dari satu daftar ke daftar lain. Itu hanya mendapatkan item dari sumber, dan itu hanya menempatkan item di target.
Berkat kekuatan kovarians dan contravariance, ini berfungsi untuk kasus seperti ini:
sumber
List<Object> myObjs = new List<Object();
(yang tidak ada penutupan>
untuk yang keduaObject
).super.methodName
. Saat menggunakan<? super E>
, itu berarti "sesuatu dalamsuper
arah" sebagai kebalikan dari sesuatu dalamextends
arah. Contoh:Object
ada disuper
arahNumber
(karena itu adalah kelas super) danInteger
berada diextends
arah (karena meluasNumber
).<? extends E>
mendefinisikanE
sebagai batas atas: "Ini dapat dilemparkan keE
".<? super E>
mendefinisikanE
sebagai batas bawah: "E
dapat digunakan untuk ini."sumber
Object
secara inheren kelas bawah, meskipun posisinya sebagai superclass tertinggi (dan secara vertikal digambar dalam UML atau pohon pewarisan serupa). Saya tidak pernah bisa membatalkan ini meskipun sudah ribuan tahun mencoba.Saya akan mencoba dan menjawab ini. Tetapi untuk mendapatkan jawaban yang benar-benar bagus Anda harus memeriksa buku Joshua Bloch, Effective Java (2nd Edition). Dia menggambarkan Pecs mnemonik, yang merupakan singkatan dari "Produser Extends, Consumer Super".
Idenya adalah bahwa jika Anda menggunakan kode nilai generik dari objek maka Anda harus menggunakan extends. tetapi jika Anda menghasilkan nilai baru untuk tipe generik Anda harus menggunakan super.
Jadi misalnya:
Dan
Tapi sungguh Anda harus memeriksa buku ini: http://java.sun.com/docs/books/effective/
sumber
<? super E>
caraany object including E that is parent of E
<? extends E>
caraany object including E that is child of E .
sumber
Anda mungkin ingin google untuk istilah contravariance (
<? super E>
) dan covariance (<? extends E>
). Saya menemukan bahwa hal yang paling berguna ketika memahami obat generik adalah bagi saya untuk memahami metode tanda tanganCollection.addAll
:Sama seperti Anda ingin dapat menambahkan
String
keList<Object>
:Anda juga harus dapat menambahkan
List<String>
(atau koleksiString
s) melaluiaddAll
metode:Namun Anda harus menyadari bahwa a
List<Object>
dan aList<String>
tidak setara dan tidak juga yang terakhir adalah subclass dari yang pertama. Apa yang dibutuhkan adalah konsep parameter tipe kovarian - yaitu<? extends T>
bit.Setelah Anda memiliki ini, mudah untuk memikirkan skenario di mana Anda ingin contravariance juga (periksa
Comparable
antarmuka).sumber
Sebelum jawabannya; Harap jelaskan itu
Contoh:
Semoga ini bisa membantu Anda memahami wildcard lebih jelas.
sumber
Wildcard dengan batas atas terlihat seperti "? Extends Type" dan singkatan dari semua jenis subtipe Type, tipe Type yang disertakan. Jenis ini disebut batas atas.
Wildcard dengan batas bawah terlihat seperti "? Super Type" dan singkatan dari keluarga semua jenis yang supertypes dari Type, type Type yang disertakan. Jenis ini disebut batas bawah.
sumber
Anda memiliki kelas Induk dan kelas Anak yang diwarisi dari kelas Induk. Kelas Induk diwarisi dari kelas lain yang disebut Kelas GrandParent. Jadi urutan pewarisannya adalah GrandParent> Parent> Child. Sekarang, <? extends Parent> - Ini menerima kelas Induk atau kelas Anak <? super Parent> - Ini menerima kelas Induk atau kelas GrandParent
sumber