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 menggunakannya super
. Jika Anda melakukan keduanya dengan koleksi yang sama, Anda tidak boleh menggunakan salah satu extends
atau 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 Thing
ketika 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 Thing
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 menahan Thing
tidak peduli apa jenis parameter yang sebenarnya. Di sini Anda tidak peduli apa yang sudah ada dalam daftar selama itu akan memungkinkan Thing
untuk ditambahkan; inilah yang ? super Thing
menjamin.
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).
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
PECS (Produser extends
dan Konsumen super
)
mnemonic → prinsip Get and Put.
Prinsip ini menyatakan bahwa:
Contoh di Jawa:
class Super {
Object testCoVariance(){ return null;} //Covariance of return types in the subtype.
void testContraVariance(Object parameter){} // Contravariance of method arguments in the subtype.
}
class Sub extends Super {
@Override
String testCoVariance(){ return null;} //compiles successfully i.e. return type is don't care(String is subtype of Object)
@Override
void 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
Untuk menggambarkan fenomena umum ini, pertimbangkan jenis array. Untuk tipe Hewan kita bisa membuat tipe Hewan []
Contoh Java:
Object name= new String("prem"); //works
List<Number> numbers = new ArrayList<Integer>();//gets compile time error
Integer[] 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=new ArrayList<>();
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:
?
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 dari T
) - wildcard dengan batas atas . T
adalah kelas paling atas dalam hierarki warisan. Gunakan extends
wildcard ketika Anda hanya mendapatkan nilai dari struktur.? super T
(keluarga dari semua jenis yang merupakan supertipe T
) - wildcard dengan batas bawah . T
adalah kelas paling bawah dalam hierarki warisan. Gunakan super
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 gunakan T
)
class Shape { void draw() {}}
class Circle extends Shape {void draw() {}}
class Square extends Shape {void draw() {}}
class Rectangle extends Shape {void draw() {}}
public class Test {
/*
* Example for an upper bound wildcard (Get values i.e Producer `extends`)
*
* */
public void testCoVariance(List<? extends Shape> list) {
list.add(new Shape()); // Error: is not applicable for the arguments (Shape) i.e. inheritance is not supporting
list.add(new Circle()); // Error: is not applicable for the arguments (Circle) i.e. inheritance is not supporting
list.add(new Square()); // Error: is not applicable for the arguments (Square) i.e. inheritance is not supporting
list.add(new Rectangle()); // Error: is not applicable for the arguments (Rectangle) i.e. inheritance is not supporting
Shape 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`)
* */
public void testContraVariance(List<? super Shape> list) {
list.add(new Shape());//compiles i.e. inheritance is supporting
list.add(new Circle());//compiles i.e. inheritance is supporting
list.add(new Square());//compiles i.e. inheritance is supporting
list.add(new Rectangle());//compiles i.e. inheritance is supporting
Shape shape= list.get(0); // Error: Type mismatch, so list acts only as consumer
Object 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.
*/
}
}
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
.
?
- "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 null
Anda tidak dapat menambahkan ke Koleksi dengan parameter ?
.
public class Test {
public class A {}
public class B extends A {}
public class C extends B {}
public void 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);
}
public void 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
}
}
? 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 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 subkelasT
juga aT
.Ketika tipe parametered yang diteruskan ke suatu metode akan mengkonsumsi instance
T
(mereka akan diteruskan untuk melakukan sesuatu),? super T
harus digunakan karena instanceT
dapat secara legal diteruskan ke metode apa pun yang menerima beberapa supertype dariT
. AComparator<Number>
dapat digunakan padaCollection<Integer>
, misalnya.? extends T
tidak akan berfungsi, karena aComparator<Integer>
tidak dapat beroperasi pada aCollection<Number>
.
Perhatikan bahwa secara umum Anda hanya boleh menggunakan ? extends T
dan ? super T
untuk parameter beberapa metode. Metode seharusnya hanya digunakan T
sebagai parameter tipe pada tipe pengembalian generik.
Singkatnya, tiga aturan mudah diingat PECS:
<? extends T>
wildcard jika Anda perlu mengambil objek tipe T
dari koleksi.<? super T>
wildcard jika Anda perlu memasukkan objek jenis T
dalam koleksi.mari kita asumsikan hierarki ini:
class Creature{}// X
class Animal extends Creature{}// Y
class Fish extends Animal{}// Z
class Shark extends Fish{}// A
class HammerSkark extends Shark{}// B
class DeadHammerShark extends HammerSkark{}// C
Mari kita perjelas PE - Produser Memperpanjang:
List<? extends Shark> sharks = new ArrayList<>();
Mengapa Anda tidak dapat menambahkan objek yang memperpanjang "Hiu" dalam daftar ini? Suka:
sharks.add(new HammerShark());//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(new HammerShark());
... 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<? super Shark> sharks = new ArrayList<>();
Apa dan mengapa Anda dapat menambahkan ke daftar ini?
sharks.add(new Shark());
sharks.add(new DeadHammerShark());
sharks.add(new HammerSkark());
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 works
Animal 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 T
ke List
. Kita tidak bisa membacanya.
Kami menggunakan <? extends T>
untuk membaca objek jenis sama atau di bawah T
dari 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);
// Destination
List<Integer> intList2 = new ArrayList<>();
List<Double> doublesList2 = new ArrayList<>();
List<Number> numList2 = new ArrayList<>();
// Works
copyElements1(intList,intList2); // from int to int
copyElements1(doubleList,doublesList2); // from double to double
static <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)
private static <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:
extends
untuk membaca
super
untuk 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".
src
dan 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.
"Aturan" PECS hanya memastikan bahwa yang berikut ini legal:
?
itu, dapat merujuk secara legal T
?
itu, secara hukum dapat dirujuk oleh T
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):
// 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 consumer
yang ?
wildcard (jenis yang tidak diketahui) adalah referensi - "sisi kiri" dari tugas - dan <? super T>
memastikan bahwa apapun ?
adalah, T
"IS-A"?
- yang T
dapat ditugaskan untuk itu, karena ?
adalah tipe super (atau paling banyak tipe yang sama) dengan T
.
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) dengan T
.
Menggunakan contoh kehidupan nyata (dengan beberapa penyederhanaan):
<? super FreightCarSize>
<? extends DepotSize>
Kovarian : terima subtipe
Kontravarians : terima supertipe
Tipe kovarian hanya baca, sedangkan tipe contravarian hanya menulis.
Mari kita lihat contohnya
public class A { }
//B is A
public class B extends A { }
//C is A
public class C extends A { }
Generik memungkinkan Anda untuk bekerja dengan Jenis secara dinamis dengan cara yang aman
//ListA
List<A> listA = new ArrayList<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);
//ListB
List<B> listB = new ArrayList<B>();
//add
listB.add(new B());
//get
B b0 = listB.get(0);
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;
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
List<? super A> listSuperA;
listSuperA = listA;
listSuperA = new ArrayList<Object>();
//add
listSuperA.add(new A());
listSuperA.add(new B());
//get
Object o0 = listSuperA.get(0);
Solusi # 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 melemparkan
List<? extends A> listExtendsA;
listExtendsA = listA;
listExtendsA = listB;
//get
A a0 = listExtendsA.get(0);
super
bagian tetapi, memberikan gagasan tentang yang lain.