Penjelasan yang sangat bagus dengan contoh @ youtube.com/watch?v=34oiEq9nD0M&feature=youtu.be&t=1630 yang menjelaskan superbagian tetapi, memberikan gagasan tentang yang lain.
lupchiazoem
Jawaban:
844
tl; dr: "PECS" adalah dari sudut pandang koleksi. Jika Anda hanya menarik item dari koleksi generik, itu adalah produsen dan Anda harus menggunakannya extends; jika Anda hanya memasukkan barang, itu adalah konsumen dan Anda harus menggunakannya super. Jika Anda melakukan keduanya dengan koleksi yang sama, Anda tidak boleh menggunakan salah satu extendsatau super.
Misalkan Anda memiliki metode yang menjadikan kumpulan parameter sebagai parameternya, tetapi Anda ingin agar lebih fleksibel daripada hanya menerima a Collection<Thing>.
Kasus 1: Anda ingin melihat-lihat koleksi dan melakukan hal-hal dengan setiap item.
Maka daftar adalah produsen , jadi Anda harus menggunakan a Collection<? extends Thing>.
Alasannya adalah bahwa a Collection<? extends Thing>dapat menampung subtipe apa pun Thing, dan dengan demikian setiap elemen akan berperilaku seperti Thingketika Anda melakukan operasi Anda. (Anda sebenarnya tidak dapat menambahkan apa pun ke a Collection<? extends Thing>, karena Anda tidak dapat mengetahui saat runtime subtipe tertentu dari Thingkoleksi tersebut.)
Kasus 2: Anda ingin menambahkan sesuatu ke koleksi.
Maka daftar tersebut adalah konsumen , jadi Anda harus menggunakan a Collection<? super Thing>.
Alasannya di sini adalah bahwa tidak seperti Collection<? extends Thing>, Collection<? super Thing>selalu dapat menahan Thingtidak peduli apa jenis parameter yang sebenarnya. Di sini Anda tidak peduli apa yang sudah ada dalam daftar selama itu akan memungkinkan Thinguntuk ditambahkan; inilah yang ? super Thingmenjamin.
Saya selalu mencoba memikirkannya dengan cara ini: Seorang produser diperbolehkan untuk menghasilkan sesuatu yang lebih spesifik, karenanya memperluas , seorang konsumen diperbolehkan untuk menerima sesuatu yang lebih umum, maka dari itu super .
Feuermurmel
10
Cara lain untuk mengingat perbedaan produsen / konsumen adalah dengan memikirkan metode tanda tangan. Jika Anda memiliki metode doSomethingWithList(List list), Anda mengkonsumsi daftar dan karenanya perlu kovarians / luas (atau Daftar invarian). Di sisi lain jika metode Anda List doSomethingProvidingList, maka Anda menghasilkan Daftar dan akan memerlukan contravariance / super (atau Daftar invarian).
Raman
3
@MichaelMyers: Mengapa kita tidak bisa menggunakan tipe parameter saja untuk kedua kasus ini? Apakah ada keuntungan spesifik menggunakan wildcard di sini, atau itu hanya cara meningkatkan keterbacaan mirip dengan, katakanlah, menggunakan referensi constsebagai parameter metode dalam C ++ untuk menandakan bahwa metode tersebut tidak mengubah argumen?
Chatterjee
7
@Aman, saya pikir Anda hanya bingung. Di doSthWithList (Anda dapat memiliki List <? Super Thing>), karena Anda seorang konsumen, Anda dapat menggunakan super (ingat, CS). Namun, itu Daftar <? extends Thing> getList () karena Anda diizinkan untuk mengembalikan sesuatu yang lebih spesifik saat memproduksi (PE).
masterxilo
4
@AZ_ Saya membagikan sentimen Anda. Jika suatu metode mendapatkan () dari daftar, metode tersebut akan dianggap sebagai Konsumen <T>, dan daftar tersebut dianggap sebagai penyedia; tetapi aturan Pecs adalah "dari sudut pandang daftar", oleh karena itu 'meluas' diperlukan. Itu harus GEPS: dapatkan ekstensi; menempatkan super.
Treefish Zhang
561
Prinsip di balik ini dalam ilmu komputer disebut
Kovarian: ? extends MyClass,
Kontravarian: ? super MyClassdan
Invarian / non-varians: MyClass
Gambar di bawah ini harus menjelaskan konsepnya. Foto milik: Andrey Tyukin
Hai semuanya. Saya Andrey Tyukin, saya hanya ingin mengkonfirmasi bahwa anoopelias & DaoWen menghubungi saya dan mendapatkan izin untuk menggunakan sketsa, itu dilisensikan di bawah (CC) -BY-SA. Thx @ Anoop untuk memberinya kehidupan kedua ^^ @Brian Agnew: (pada "beberapa suara"): Itu karena ini adalah sketsa untuk Scala, ia menggunakan sintaksis Scala dan mengasumsikan varian situs deklarasi, yang sangat berbeda dengan panggilan aneh Jawa -situs varians ... Mungkin saya harus menulis jawaban yang lebih rinci yang dengan jelas menunjukkan bagaimana sketsa ini berlaku untuk Jawa ...
Andrey Tyukin
3
Ini adalah salah satu penjelasan paling sederhana dan paling jelas untuk Covariance dan Contravariance yang pernah saya temukan!
cs4r
@Andrey Tyukin Hai, saya juga ingin menggunakan gambar ini. Bagaimana saya bisa menghubungi Anda?
Gunakan wildcard extends ketika Anda hanya mendapatkan nilai dari struktur.
Gunakan super wildcard ketika Anda hanya memasukkan nilai ke dalam struktur.
Dan jangan gunakan wildcard saat Anda berdua mendapatkan dan memasukkan.
Contoh di Jawa:
classSuper{Object testCoVariance(){returnnull;}//Covariance of return types in the subtype.void testContraVariance(Object parameter){}// Contravariance of method arguments in the subtype.}classSubextendsSuper{@OverrideString testCoVariance(){returnnull;}//compiles successfully i.e. return type is don't care(String is subtype of Object) @Overridevoid testContraVariance(String parameter){}//doesn't support even though String is subtype of Object}
Prinsip substitusi Liskov: jika S adalah subtipe dari T, maka objek tipe T dapat diganti dengan objek tipe S.
Di dalam sistem jenis bahasa pemrograman, aturan pengetikan
kovarian jika mempertahankan pemesanan jenis (≤), yang memesan jenis dari yang lebih spesifik ke yang lebih umum;
contravariant jika membalikkan pemesanan ini;
invarian atau nonvariant jika keduanya tidak berlaku.
Tipe data baca-saja (sumber) dapat bersifat kovarian ;
tipe data hanya-tulis (sink) dapat bersifat contravarian .
Jenis data yang bisa berubah yang bertindak sebagai sumber dan sumber data harus invarian .
Untuk menggambarkan fenomena umum ini, pertimbangkan jenis array. Untuk tipe Hewan kita bisa membuat tipe Hewan []
kovarian : Kucing [] adalah Hewan [];
contravarian : Hewan [] adalah Kucing [];
invarian : Hewan [] bukan Kucing [] dan Kucing [] bukan Hewan [].
Contoh Java:
Object name=newString("prem");//worksList<Number> numbers =newArrayList<Integer>();//gets compile time errorInteger[] myInts ={1,2,3,4};Number[] myNumber = myInts;
myNumber[0]=3.14;//attempt of heap pollution i.e. at runtime gets java.lang.ArrayStoreException: java.lang.Double(we can fool compiler but not run-time)List<String> list=newArrayList<>();
list.add("prem");List<Object> listObject=list;//Type mismatch: cannot convert from List<String> to List<Object> at Compiletime
wildcard dibatasi (yaitu menuju ke suatu tempat) : Ada 3 rasa wildcard yang berbeda:
In-variance / Non-variance: ?atau ? extends Object- Tidak terbatas Wildcard Tidak . Itu singkatan dari semua jenis keluarga. Gunakan saat Anda berdua mendapatkan dan meletakkan.
Co-variance: ? extends T(keluarga dari semua jenis yang merupakan subtipe dari T) - wildcard dengan batas atas . Tadalah kelas paling atas dalam hierarki warisan. Gunakan extendswildcard ketika Anda hanya mendapatkan nilai dari struktur.
Contra-variance: ? super T(keluarga dari semua jenis yang merupakan supertipe T) - wildcard dengan batas bawah . Tadalah kelas paling bawah dalam hierarki warisan. Gunakan superwildcard ketika Anda hanya Masukan nilai-nilai ke dalam struktur.
Catatan: wildcard ?berarti nol atau satu kali , mewakili tipe yang tidak dikenal. Wildcard dapat digunakan sebagai tipe parameter, tidak pernah digunakan sebagai argumen tipe untuk pemanggilan metode generik, pembuatan instance kelas generik. (Yaitu ketika wildcard yang digunakan untuk referensi yang tidak digunakan di tempat lain dalam program seperti yang kita gunakan T)
classShape{void draw(){}}classCircleextendsShape{void draw(){}}classSquareextendsShape{void draw(){}}classRectangleextendsShape{void draw(){}}publicclassTest{/*
* Example for an upper bound wildcard (Get values i.e Producer `extends`)
*
* */publicvoid testCoVariance(List<?extendsShape> list){
list.add(newShape());// Error: is not applicable for the arguments (Shape) i.e. inheritance is not supporting
list.add(newCircle());// Error: is not applicable for the arguments (Circle) i.e. inheritance is not supporting
list.add(newSquare());// Error: is not applicable for the arguments (Square) i.e. inheritance is not supporting
list.add(newRectangle());// Error: is not applicable for the arguments (Rectangle) i.e. inheritance is not supportingShape shape= list.get(0);//compiles so list act as produces only/*You can't add a Shape,Circle,Square,Rectangle to a List<? extends Shape>
* You can get an object and know that it will be an Shape
*/}/*
* Example for a lower bound wildcard (Put values i.e Consumer`super`)
* */publicvoid testContraVariance(List<?superShape> list){
list.add(newShape());//compiles i.e. inheritance is supporting
list.add(newCircle());//compiles i.e. inheritance is supporting
list.add(newSquare());//compiles i.e. inheritance is supporting
list.add(newRectangle());//compiles i.e. inheritance is supportingShape shape= list.get(0);// Error: Type mismatch, so list acts only as consumerObject object= list.get(0);// gets an object, but we don't know what kind of Object it is./*You can add a Shape,Circle,Square,Rectangle to a List<? super Shape>
* You can't get an Shape(but can get Object) and don't know what kind of Shape it is.
*/}}
Hei, saya hanya ingin tahu apa yang Anda maksud dengan kalimat terakhir: "Jika Anda pikir analogi saya salah, mohon perbarui". Apakah maksud Anda jika secara etis salah (yang subjektif) atau jika salah dalam konteks pemrograman (yang objektif: tidak, tidak salah)? Saya ingin menggantinya dengan contoh yang lebih netral yang dapat diterima secara universal terlepas dari norma-norma budaya dan kepercayaan etis; Jika itu tidak masalah dengan Anda.
Neuron
akhirnya saya bisa mendapatkannya. Penjelasan yang bagus.
Oleg Kuts
2
@ Primrem,, In-variance/Non-variance: ? or ? extends Object - Unbounded Wildcard. It stands for the family of all types. Use when you both get and put.saya tidak bisa menambahkan elemen ke Daftar <?> Atau Daftar <? meluas Object>, jadi saya tidak mengerti mengapa itu bisa terjadi Use when you both get and put.
LiuWenbin_NO.
1
@LiuWenbin_NO. - Bagian dari jawaban itu menyesatkan. ?- "wildcard tak terbatas" - sesuai dengan kebalikan dari invarian. Silakan merujuk ke dokumentasi berikut: docs.oracle.com/javase/tutorial/java/generics/… yang menyatakan: Dalam kasus di mana kode perlu mengakses variabel sebagai variabel "dalam" dan "keluar", lakukan tidak menggunakan wildcard. (Mereka menggunakan "in" dan "out" sebagai sinonim dengan "get" dan "put"). Dengan pengecualian nullAnda tidak dapat menambahkan ke Koleksi dengan parameter ?.
mouselabs
29
publicclassTest{publicclass A {}publicclass B extends A {}publicclass C extends B {}publicvoid testCoVariance(List<?extends B> myBlist){
B b =new B();
C c =new C();
myBlist.add(b);// does not compile
myBlist.add(c);// does not compile
A a = myBlist.get(0);}publicvoid testContraVariance(List<?super B> myBlist){
B b =new B();
C c =new C();
myBlist.add(b);
myBlist.add(c);
A a = myBlist.get(0);// does not compile}}
Jadi "? Extends B" harus ditafsirkan sebagai "? B extends". Itu adalah sesuatu yang diperluas B sehingga akan mencakup semua kelas super B hingga Objek, tidak termasuk B itu sendiri. Terima kasih untuk kodenya!
Saurabh Patil
3
@SaurabhPatil Tidak, ? extends Bberarti B dan apa pun yang memperpanjang B.
asgs
24
Seperti yang saya jelaskan di jawaban saya ke pertanyaan lain, Pecs adalah perangkat mnemonik yang dibuat oleh Josh Bloch untuk bantuan ingat P roducer extends, C onsumer super.
Ini berarti bahwa ketika tipe parameter yang dilewatkan ke suatu metode akan menghasilkan instance T(mereka akan diambil darinya dengan beberapa cara),? extends T harus digunakan, karena setiap instance dari subkelas Tjuga a T.
Ketika tipe parametered yang diteruskan ke suatu metode akan mengkonsumsi instance T(mereka akan diteruskan untuk melakukan sesuatu), ? super Tharus digunakan karena instance Tdapat secara legal diteruskan ke metode apa pun yang menerima beberapa supertype dari T. A Comparator<Number>dapat digunakan pada Collection<Integer>, misalnya. ? extends Ttidak akan berfungsi, karena a Comparator<Integer>tidak dapat beroperasi pada aCollection<Number> .
Perhatikan bahwa secara umum Anda hanya boleh menggunakan ? extends Tdan ? super Tuntuk parameter beberapa metode. Metode seharusnya hanya digunakan Tsebagai parameter tipe pada tipe pengembalian generik.
Apakah prinsip ini hanya berlaku untuk Koleksi? Masuk akal ketika seseorang mencoba menghubungkannya dengan daftar. Jika Anda berpikir tentang tanda tangan sortir (List <T>, Comparator <? Super T>) ---> di sini Comparator menggunakan super sehingga itu berarti konsumen dalam konteks Pecs. Ketika Anda melihat implementasi misalnya seperti: public int bandingkan (Orang a, Orang b) {return a.age <b.age? -1: a.age == b.age? 0: 1; } Saya merasa seperti Orang tidak mengkonsumsi apa pun selain menghasilkan usia. Itu membuat saya bingung. Apakah ada kesalahan dalam alasan saya atau Pecs hanya berlaku untuk Koleksi?
Fatih Arslan
24
Singkatnya, tiga aturan mudah diingat PECS:
Gunakan <? extends T>wildcard jika Anda perlu mengambil objek tipe Tdari koleksi.
Gunakan <? super T>wildcard jika Anda perlu memasukkan objek jenis Tdalam koleksi.
Jika Anda perlu memuaskan kedua hal itu, yah, jangan gunakan wildcard apa pun. Sesimpel itu.
classCreature{}// XclassAnimalextendsCreature{}// YclassFishextendsAnimal{}// ZclassSharkextendsFish{}// AclassHammerSkarkextendsShark{}// BclassDeadHammerSharkextendsHammerSkark{}// C
Mari kita perjelas PE - Produser Memperpanjang:
List<?extendsShark> sharks =newArrayList<>();
Mengapa Anda tidak dapat menambahkan objek yang memperpanjang "Hiu" dalam daftar ini? Suka:
sharks.add(newHammerShark());//will result in compilation error
Karena Anda memiliki daftar yang bisa tipe A, B atau C saat runtime , Anda tidak bisa menambahkan objek tipe A, B atau C di dalamnya karena Anda bisa berakhir dengan kombinasi yang tidak diperbolehkan di java. Dalam praktiknya, kompiler memang dapat melihat pada saat bersamaan Anda menambahkan B:
sharks.add(newHammerShark());
... tetapi tidak memiliki cara untuk mengetahui apakah pada saat runtime, B Anda akan menjadi subtipe atau supertipe dari tipe daftar. Saat runtime, tipe daftar bisa berupa tipe A, B, C. Jadi, Anda tidak dapat menambahkan HammerSkark (tipe super) di daftar DeadHammerShark misalnya.
* Anda akan berkata: "OK, tapi mengapa saya tidak bisa menambahkan HammerSkark di dalamnya karena ini adalah jenis terkecil?". Jawab: Ini yang terkecil yang kamu tahu. Tapi HammerSkark dapat diperpanjang juga oleh orang lain dan Anda berakhir dalam skenario yang sama.
Mari kita perjelas CS - Consumer Super:
Dalam hierarki yang sama kita dapat mencoba ini:
List<?superShark> sharks =newArrayList<>();
Apa dan mengapa Anda dapat menambahkan ke daftar ini?
Anda dapat menambahkan jenis objek di atas karena apa pun di bawah hiu (A, B, C) akan selalu menjadi subtipe dari apa pun di atas hiu (X, Y, Z). Mudah dimengerti.
Anda tidak dapat menambahkan jenis di atas Hiu, karena pada saat runtime jenis objek yang ditambahkan dapat lebih tinggi dalam hierarki daripada jenis daftar yang dinyatakan (X, Y, Z). Ini tidak diizinkan
Tetapi mengapa Anda tidak dapat membaca dari daftar ini? (Maksud saya Anda bisa mendapatkan elemen dari itu, tetapi Anda tidak bisa menugaskannya selain dari Objek o):
Object o;
o = sharks.get(2);// only assignment that worksAnimal s;
s = sharks.get(2);//doen't work
Saat runtime, tipe daftar dapat berupa tipe apa pun di atas A: X, Y, Z, ... Compiler dapat mengkompilasi pernyataan penugasan Anda (yang tampaknya benar) tetapi, pada saat runtime tipe s (Hewan) dapat lebih rendah di hierarki dari jenis daftar yang dinyatakan (yang bisa berupa Makhluk, atau lebih tinggi). Ini tidak diizinkan
Untuk menyimpulkan
Kami gunakan <? super T>untuk menambahkan objek bertipe sama atau di bawah Tke List. Kita tidak bisa membacanya. Kami menggunakan <? extends T>untuk membaca objek jenis sama atau di bawah Tdari daftar. Kami tidak dapat menambahkan elemen ke dalamnya.
(menambahkan jawaban karena tidak pernah cukup contoh dengan wildcard Generics)
// Source List<Integer> intList =Arrays.asList(1,2,3);List<Double> doubleList =Arrays.asList(2.78,3.14);List<Number> numList =Arrays.asList(1,2,2.78,3.14,5);// DestinationList<Integer> intList2 =newArrayList<>();List<Double> doublesList2 =newArrayList<>();List<Number> numList2 =newArrayList<>();// Works
copyElements1(intList,intList2);// from int to int
copyElements1(doubleList,doublesList2);// from double to doublestatic<T>void copyElements1(Collection<T> src,Collection<T> dest){for(T n : src){
dest.add(n);}}// Let's try to copy intList to its supertype
copyElements1(intList,numList2);// error, method signature just says "T"// and here the compiler is given // two types: Integer and Number, // so which one shall it be?// PECS to the rescue!
copyElements2(intList,numList2);// possible// copy Integer (? extends T) to its supertype (Number is super of Integer)privatestatic<T>void copyElements2(Collection<?extends T> src,Collection<?super T> dest){for(T n : src){
dest.add(n);}}
Ini adalah cara yang paling jelas, paling sederhana bagi saya untuk berpikir tentang ext vs super:
extendsuntuk membaca
superuntuk menulis
Saya menemukan "Pecs" menjadi cara yang tidak jelas untuk memikirkan hal-hal mengenai siapa yang "produsen" dan siapa yang "konsumen". "PECS" didefinisikan dari perspektif pengumpulan data itu sendiri - koleksi "mengkonsumsi" jika objek sedang ditulis untuk itu (itu memakan objek dari memanggil kode), dan itu "menghasilkan" jika objek sedang dibaca dari itu (itu memproduksi objek ke beberapa kode panggilan). Ini bertentangan dengan bagaimana segala sesuatu disebut. API Java standar dinamai dari perspektif kode panggilan, bukan koleksi itu sendiri. Misalnya, tampilan kumpulan-sentris dari java.util.List harus memiliki metode bernama "accept ()" alih-alih "add ()" - setelah semua,menerima elemen.
Saya pikir itu lebih intuitif, alami dan konsisten untuk memikirkan hal-hal dari perspektif kode yang berinteraksi dengan koleksi - apakah kode "membaca dari" atau "menulis ke" koleksi? Setelah itu, setiap penulisan kode ke koleksi akan menjadi "produsen", dan setiap pembacaan kode dari koleksi akan menjadi "konsumen".
Aku telah lari ke yang tabrakan mental yang sama dan akan cenderung setuju kecuali bahwa Pecs tidak menentukan penamaan kode dan jenis batas-batas sendiri yang ditetapkan pada deklarasi Collection. Selain itu menyangkut penamaan Anda sering memiliki nama untuk memproduksi / mengkonsumsi Koleksi seperti srcdan dst. Jadi Anda berurusan dengan kode dan kontainer pada saat yang sama dan saya akhirnya memikirkannya di sepanjang garis - "kode konsumsi" mengkonsumsi dari wadah produksi, dan "memproduksi kode" memproduksi untuk wadah konsumsi.
mouselabs
4
"Aturan" PECS hanya memastikan bahwa yang berikut ini legal:
Konsumen: apa pun ?itu, dapat merujuk secara legal T
Produser: apa pun ?itu, secara hukum dapat dirujuk olehT
Pasangan tipikal sepanjang garis List<? extends T> producer, List<? super T> consumerhanya memastikan bahwa kompiler dapat menegakkan aturan hubungan warisan "IS-A" standar. Jika kita dapat melakukannya secara legal, mungkin lebih mudah untuk mengatakan <T extends ?>, <? extends T>(atau lebih baik lagi di Scala, seperti yang Anda lihat di atas, itu [-T], [+T]. Sayangnya yang terbaik yang bisa kita lakukan adalah <? super T>, <? extends T>.
Ketika saya pertama kali menemukan ini dan memecahnya di kepala saya mekanika masuk akal tetapi kode itu sendiri terus terlihat membingungkan bagi saya - saya terus berpikir "sepertinya batas tidak perlu terbalik seperti itu" - meskipun saya sudah jelas di atas - bahwa itu hanya tentang jaminan kepatuhan dengan aturan referensi standar.
Apa yang membantu saya melihatnya menggunakan tugas biasa sebagai analogi.
Pertimbangkan kode mainan berikut (belum siap produksi):
// copies the elements of 'producer' into 'consumer'static<T>void copy(List<?extends T> producer,List<?super T> consumer){for(T t : producer)
consumer.add(t);}
Menggambarkan ini dalam hal analogi tugas, untuk consumeryang ?wildcard (jenis yang tidak diketahui) adalah referensi - "sisi kiri" dari tugas - dan <? super T>memastikan bahwa apapun ?adalah, T"IS-A"? - yang Tdapat ditugaskan untuk itu, karena ?adalah tipe super (atau paling banyak tipe yang sama) dengan T.
Untuk producerperhatian adalah sama hanya saja terbalik: producer's ?wildcard (tidak diketahui jenis) adalah rujukan - 'sisi kanan' dari tugas - dan <? extends T>memastikan bahwa apapun ?adalah, ?'IS-A' T- yang itu dapat ditugaskan keT , karena ?merupakan jenis sub (atau setidaknya jenis yang sama) dengan T.
publicclass A {}//B is Apublicclass B extends A {}//C is Apublicclass C extends A {}
Generik memungkinkan Anda untuk bekerja dengan Jenis secara dinamis dengan cara yang aman
//ListAList<A> listA =newArrayList<A>();//add
listA.add(new A());
listA.add(new B());
listA.add(new C());//get
A a0 = listA.get(0);
A a1 = listA.get(1);
A a2 = listA.get(2);
//ListBList<B> listB =newArrayList<B>();//add
listB.add(new B());//get
B b0 = listB.get(0);
Masalah
Karena Java's Collection adalah tipe referensi, kami memiliki masalah berikut:
Masalah # 1
//not compiled//danger of **adding** non-B objects using listA reference
listA = listB;
* Generik Swift tidak memiliki masalah seperti itu karena Koleksi adalah Value type[Tentang] oleh karena itu koleksi baru dibuat
Masalah # 2
//not compiled//danger of **getting** non-B objects using listB reference
listB = listA;
Solusinya - Generik Wildcard
Wildcard adalah fitur tipe referensi dan tidak dapat dipakai secara langsung
Solusi # 1<? super A> alias batas bawah alias contravariance alias konsumen menjamin bahwa itu dioperasikan oleh A dan semua superclasses, oleh karena itu aman untuk ditambahkan
<? extends A>alias batas atas alias kovarian alias produsen menjamin bahwa itu dioperasikan oleh A dan semua subclass, itu sebabnya aman untuk mendapatkan dan melemparkan
List<?extends A> listExtendsA;
listExtendsA = listA;
listExtendsA = listB;//get
A a0 = listExtendsA.get(0);
super
bagian tetapi, memberikan gagasan tentang yang lain.Jawaban:
tl; dr: "PECS" adalah dari sudut pandang koleksi. Jika Anda hanya menarik item dari koleksi generik, itu adalah produsen dan Anda harus menggunakannya
extends
; jika Anda hanya memasukkan barang, itu adalah konsumen dan Anda harus menggunakannyasuper
. Jika Anda melakukan keduanya dengan koleksi yang sama, Anda tidak boleh menggunakan salah satuextends
atausuper
.Misalkan Anda memiliki metode yang menjadikan kumpulan parameter sebagai parameternya, tetapi Anda ingin agar lebih fleksibel daripada hanya menerima a
Collection<Thing>
.Kasus 1: Anda ingin melihat-lihat koleksi dan melakukan hal-hal dengan setiap item.
Maka daftar adalah produsen , jadi Anda harus menggunakan a
Collection<? extends Thing>
.Alasannya adalah bahwa a
Collection<? extends Thing>
dapat menampung subtipe apa punThing
, dan dengan demikian setiap elemen akan berperilaku sepertiThing
ketika Anda melakukan operasi Anda. (Anda sebenarnya tidak dapat menambahkan apa pun ke aCollection<? extends Thing>
, karena Anda tidak dapat mengetahui saat runtime subtipe tertentu dariThing
koleksi tersebut.)Kasus 2: Anda ingin menambahkan sesuatu ke koleksi.
Maka daftar tersebut adalah konsumen , jadi Anda harus menggunakan a
Collection<? super Thing>
.Alasannya di sini adalah bahwa tidak seperti
Collection<? extends Thing>
,Collection<? super Thing>
selalu dapat menahanThing
tidak peduli apa jenis parameter yang sebenarnya. Di sini Anda tidak peduli apa yang sudah ada dalam daftar selama itu akan memungkinkanThing
untuk ditambahkan; inilah yang? super Thing
menjamin.sumber
doSomethingWithList(List list)
, Anda mengkonsumsi daftar dan karenanya perlu kovarians / luas (atau Daftar invarian). Di sisi lain jika metode AndaList doSomethingProvidingList
, maka Anda menghasilkan Daftar dan akan memerlukan contravariance / super (atau Daftar invarian).const
sebagai parameter metode dalam C ++ untuk menandakan bahwa metode tersebut tidak mengubah argumen?Prinsip di balik ini dalam ilmu komputer disebut
? extends MyClass
,? super MyClass
danMyClass
Gambar di bawah ini harus menjelaskan konsepnya. Foto milik: Andrey Tyukin
sumber
PECS (Produser
extends
dan Konsumensuper
)mnemonic → prinsip Get and Put.
Prinsip ini menyatakan bahwa:
Contoh di Jawa:
Prinsip substitusi Liskov: jika S adalah subtipe dari T, maka objek tipe T dapat diganti dengan objek tipe S.
Di dalam sistem jenis bahasa pemrograman, aturan pengetikan
Kovarian dan contravariance
Untuk menggambarkan fenomena umum ini, pertimbangkan jenis array. Untuk tipe Hewan kita bisa membuat tipe Hewan []
Contoh Java:
lebih banyak contoh
wildcard dibatasi (yaitu menuju ke suatu tempat) : Ada 3 rasa wildcard yang berbeda:
?
atau? extends Object
- Tidak terbatas Wildcard Tidak . Itu singkatan dari semua jenis keluarga. Gunakan saat Anda berdua mendapatkan dan meletakkan.? extends T
(keluarga dari semua jenis yang merupakan subtipe dariT
) - wildcard dengan batas atas .T
adalah kelas paling atas dalam hierarki warisan. Gunakanextends
wildcard ketika Anda hanya mendapatkan nilai dari struktur.? super T
(keluarga dari semua jenis yang merupakan supertipeT
) - wildcard dengan batas bawah .T
adalah kelas paling bawah dalam hierarki warisan. Gunakansuper
wildcard ketika Anda hanya Masukan nilai-nilai ke dalam struktur.Catatan: wildcard
?
berarti nol atau satu kali , mewakili tipe yang tidak dikenal. Wildcard dapat digunakan sebagai tipe parameter, tidak pernah digunakan sebagai argumen tipe untuk pemanggilan metode generik, pembuatan instance kelas generik. (Yaitu ketika wildcard yang digunakan untuk referensi yang tidak digunakan di tempat lain dalam program seperti yang kita gunakanT
)generik dan contoh
sumber
In-variance/Non-variance: ? or ? extends Object - Unbounded Wildcard. It stands for the family of all types. Use when you both get and put.
saya tidak bisa menambahkan elemen ke Daftar <?> Atau Daftar <? meluas Object>, jadi saya tidak mengerti mengapa itu bisa terjadiUse when you both get and put
.?
- "wildcard tak terbatas" - sesuai dengan kebalikan dari invarian. Silakan merujuk ke dokumentasi berikut: docs.oracle.com/javase/tutorial/java/generics/… yang menyatakan: Dalam kasus di mana kode perlu mengakses variabel sebagai variabel "dalam" dan "keluar", lakukan tidak menggunakan wildcard. (Mereka menggunakan "in" dan "out" sebagai sinonim dengan "get" dan "put"). Dengan pengecualiannull
Anda tidak dapat menambahkan ke Koleksi dengan parameter?
.sumber
? extends B
berarti B dan apa pun yang memperpanjang B.Seperti yang saya jelaskan di jawaban saya ke pertanyaan lain, Pecs adalah perangkat mnemonik yang dibuat oleh Josh Bloch untuk bantuan ingat P roducer
extends
, C onsumersuper
.Perhatikan bahwa secara umum Anda hanya boleh menggunakan
? extends T
dan? super T
untuk parameter beberapa metode. Metode seharusnya hanya digunakanT
sebagai parameter tipe pada tipe pengembalian generik.sumber
Singkatnya, tiga aturan mudah diingat PECS:
<? extends T>
wildcard jika Anda perlu mengambil objek tipeT
dari koleksi.<? super T>
wildcard jika Anda perlu memasukkan objek jenisT
dalam koleksi.sumber
mari kita asumsikan hierarki ini:
Mari kita perjelas PE - Produser Memperpanjang:
Mengapa Anda tidak dapat menambahkan objek yang memperpanjang "Hiu" dalam daftar ini? Suka:
Karena Anda memiliki daftar yang bisa tipe A, B atau C saat runtime , Anda tidak bisa menambahkan objek tipe A, B atau C di dalamnya karena Anda bisa berakhir dengan kombinasi yang tidak diperbolehkan di java.
Dalam praktiknya, kompiler memang dapat melihat pada saat bersamaan Anda menambahkan B:
... tetapi tidak memiliki cara untuk mengetahui apakah pada saat runtime, B Anda akan menjadi subtipe atau supertipe dari tipe daftar. Saat runtime, tipe daftar bisa berupa tipe A, B, C. Jadi, Anda tidak dapat menambahkan HammerSkark (tipe super) di daftar DeadHammerShark misalnya.
* Anda akan berkata: "OK, tapi mengapa saya tidak bisa menambahkan HammerSkark di dalamnya karena ini adalah jenis terkecil?". Jawab: Ini yang terkecil yang kamu tahu. Tapi HammerSkark dapat diperpanjang juga oleh orang lain dan Anda berakhir dalam skenario yang sama.
Mari kita perjelas CS - Consumer Super:
Dalam hierarki yang sama kita dapat mencoba ini:
Apa dan mengapa Anda dapat menambahkan ke daftar ini?
Anda dapat menambahkan jenis objek di atas karena apa pun di bawah hiu (A, B, C) akan selalu menjadi subtipe dari apa pun di atas hiu (X, Y, Z). Mudah dimengerti.
Anda tidak dapat menambahkan jenis di atas Hiu, karena pada saat runtime jenis objek yang ditambahkan dapat lebih tinggi dalam hierarki daripada jenis daftar yang dinyatakan (X, Y, Z). Ini tidak diizinkan
Tetapi mengapa Anda tidak dapat membaca dari daftar ini? (Maksud saya Anda bisa mendapatkan elemen dari itu, tetapi Anda tidak bisa menugaskannya selain dari Objek o):
Saat runtime, tipe daftar dapat berupa tipe apa pun di atas A: X, Y, Z, ... Compiler dapat mengkompilasi pernyataan penugasan Anda (yang tampaknya benar) tetapi, pada saat runtime tipe s (Hewan) dapat lebih rendah di hierarki dari jenis daftar yang dinyatakan (yang bisa berupa Makhluk, atau lebih tinggi). Ini tidak diizinkan
Untuk menyimpulkan
Kami gunakan
<? super T>
untuk menambahkan objek bertipe sama atau di bawahT
keList
. Kita tidak bisa membacanya.Kami menggunakan
<? extends T>
untuk membaca objek jenis sama atau di bawahT
dari daftar. Kami tidak dapat menambahkan elemen ke dalamnya.sumber
(menambahkan jawaban karena tidak pernah cukup contoh dengan wildcard Generics)
sumber
Ini adalah cara yang paling jelas, paling sederhana bagi saya untuk berpikir tentang ext vs super:
extends
untuk membacasuper
untuk menulisSaya menemukan "Pecs" menjadi cara yang tidak jelas untuk memikirkan hal-hal mengenai siapa yang "produsen" dan siapa yang "konsumen". "PECS" didefinisikan dari perspektif pengumpulan data itu sendiri - koleksi "mengkonsumsi" jika objek sedang ditulis untuk itu (itu memakan objek dari memanggil kode), dan itu "menghasilkan" jika objek sedang dibaca dari itu (itu memproduksi objek ke beberapa kode panggilan). Ini bertentangan dengan bagaimana segala sesuatu disebut. API Java standar dinamai dari perspektif kode panggilan, bukan koleksi itu sendiri. Misalnya, tampilan kumpulan-sentris dari java.util.List harus memiliki metode bernama "accept ()" alih-alih "add ()" - setelah semua,menerima elemen.
Saya pikir itu lebih intuitif, alami dan konsisten untuk memikirkan hal-hal dari perspektif kode yang berinteraksi dengan koleksi - apakah kode "membaca dari" atau "menulis ke" koleksi? Setelah itu, setiap penulisan kode ke koleksi akan menjadi "produsen", dan setiap pembacaan kode dari koleksi akan menjadi "konsumen".
sumber
src
dandst
. Jadi Anda berurusan dengan kode dan kontainer pada saat yang sama dan saya akhirnya memikirkannya di sepanjang garis - "kode konsumsi" mengkonsumsi dari wadah produksi, dan "memproduksi kode" memproduksi untuk wadah konsumsi."Aturan" PECS hanya memastikan bahwa yang berikut ini legal:
?
itu, dapat merujuk secara legalT
?
itu, secara hukum dapat dirujuk olehT
Pasangan tipikal sepanjang garis
List<? extends T> producer, List<? super T> consumer
hanya memastikan bahwa kompiler dapat menegakkan aturan hubungan warisan "IS-A" standar. Jika kita dapat melakukannya secara legal, mungkin lebih mudah untuk mengatakan<T extends ?>, <? extends T>
(atau lebih baik lagi di Scala, seperti yang Anda lihat di atas, itu[-T], [+T]
. Sayangnya yang terbaik yang bisa kita lakukan adalah<? super T>, <? extends T>
.Ketika saya pertama kali menemukan ini dan memecahnya di kepala saya mekanika masuk akal tetapi kode itu sendiri terus terlihat membingungkan bagi saya - saya terus berpikir "sepertinya batas tidak perlu terbalik seperti itu" - meskipun saya sudah jelas di atas - bahwa itu hanya tentang jaminan kepatuhan dengan aturan referensi standar.
Apa yang membantu saya melihatnya menggunakan tugas biasa sebagai analogi.
Pertimbangkan kode mainan berikut (belum siap produksi):
Menggambarkan ini dalam hal analogi tugas, untuk
consumer
yang?
wildcard (jenis yang tidak diketahui) adalah referensi - "sisi kiri" dari tugas - dan<? super T>
memastikan bahwa apapun?
adalah,T
"IS-A"?
- yangT
dapat ditugaskan untuk itu, karena?
adalah tipe super (atau paling banyak tipe yang sama) denganT
.Untuk
producer
perhatian adalah sama hanya saja terbalik:producer
's?
wildcard (tidak diketahui jenis) adalah rujukan - 'sisi kanan' dari tugas - dan<? extends T>
memastikan bahwa apapun?
adalah,?
'IS-A'T
- yang itu dapat ditugaskan keT
, karena?
merupakan jenis sub (atau setidaknya jenis yang sama) denganT
.sumber
Ingat ini:
sumber
Menggunakan contoh kehidupan nyata (dengan beberapa penyederhanaan):
<? super FreightCarSize>
<? extends DepotSize>
sumber
Kovarian : terima subtipe
Kontravarians : terima supertipe
Tipe kovarian hanya baca, sedangkan tipe contravarian hanya menulis.
sumber
Mari kita lihat contohnya
Generik memungkinkan Anda untuk bekerja dengan Jenis secara dinamis dengan cara yang aman
Masalah
Karena Java's Collection adalah tipe referensi, kami memiliki masalah berikut:
Masalah # 1
* Generik Swift tidak memiliki masalah seperti itu karena Koleksi adalah
Value type
[Tentang] oleh karena itu koleksi baru dibuatMasalah # 2
Solusinya - Generik Wildcard
Wildcard adalah fitur tipe referensi dan tidak dapat dipakai secara langsung
Solusi # 1
<? super A>
alias batas bawah alias contravariance alias konsumen menjamin bahwa itu dioperasikan oleh A dan semua superclasses, oleh karena itu aman untuk ditambahkanSolusi # 2
<? extends A>
alias batas atas alias kovarian alias produsen menjamin bahwa itu dioperasikan oleh A dan semua subclass, itu sebabnya aman untuk mendapatkan dan melemparkansumber