Odpowiedzi:
tl; dr: „PECS” jest z punktu widzenia kolekcji. Jeśli wyciągasz tylko przedmioty z ogólnej kolekcji, jest to producent i powinieneś użyć extends
; jeśli pakujesz tylko przedmioty, jest to konsument i powinieneś go użyć super
. Jeśli zrobić zarówno z tej samej kolekcji, nie należy używać albo extends
albo super
.
Załóżmy, że masz metodę, która przyjmuje za swój parametr zbiór rzeczy, ale chcesz, aby była bardziej elastyczna niż tylko akceptowanie Collection<Thing>
.
Przypadek 1: Chcesz przejrzeć kolekcję i robić rzeczy z każdym przedmiotem.
Zatem lista jest producentem , więc powinieneś użyć Collection<? extends Thing>
.
Powodem jest to, że a Collection<? extends Thing>
może zawierać dowolny podtyp Thing
, a zatem każdy element będzie zachowywał się jak Thing
podczas wykonywania operacji. (Tak naprawdę nie możesz nic dodać do Collection<? extends Thing>
, ponieważ nie możesz wiedzieć w czasie wykonywania, który konkretny podtyp Thing
kolekcji zawiera.)
Przypadek 2: Chcesz dodać rzeczy do kolekcji.
Zatem lista jest konsumentem , więc powinieneś użyć Collection<? super Thing>
.
Rozumowanie jest tutaj takie, że w przeciwieństwie do Collection<? extends Thing>
, Collection<? super Thing>
zawsze może zawierać Thing
niezależnie od tego, jaki jest faktycznie sparametryzowany typ. Tutaj nie obchodzi Cię, co jest już na liście, o ile pozwoli to Thing
na dodanie; to ? super Thing
gwarantuje.
doSomethingWithList(List list)
, zużywasz listę, więc będziesz potrzebować kowariancji / rozszerzeń (lub niezmiennej listy). Z drugiej strony, jeśli jest metoda List doSomethingProvidingList
, to jesteś produkujących listy i będą musiały kontrawariancji / Super (lub niezmiennikiem listę).
const
jako parametrów metody w C ++ w celu oznaczenia, że metoda nie modyfikuje argumentów?
Nazywa się to zasadami informatyki
? extends MyClass
,? super MyClass
iMyClass
Poniższy obrazek powinien wyjaśnić tę koncepcję. Zdjęcie dzięki uprzejmości: Andrey Tyukin
PECS (producent extends
i konsument super
)
mnemonic → Zasada Get and Put.
Zasada ta stanowi, że:
Przykład w Javie:
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
}
Zasada podstawienia Liskowa: jeśli S jest podtypem T, wówczas obiekty typu T można zastąpić obiektami typu S.
W systemie typów języka programowania obowiązuje reguła pisania
Aby zilustrować to ogólne zjawisko, rozważ typ tablicy. Dla typu Animal możemy utworzyć typ Animal []
Przykłady 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
ograniczona (tj. zmierzająca gdzieś) symbol wieloznaczny : Istnieją 3 różne smaki symboli wieloznacznych:
?
lub ? extends Object
- Unbounded Wildcard. Oznacza rodzinę wszystkich typów. Użyj, gdy oboje weźmiesz i umieścisz.? extends T
(rodzina wszystkich typów, które są podtypami T
) - symbol wieloznaczny z górną granicą . T
jest najwyższą klasą w hierarchii dziedziczenia. Użyj extends
symbolu wieloznacznego, gdy tylko pobierasz wartości ze struktury.? super T
(rodzina wszystkich typów, które są nadtypami T
) - symbol wieloznaczny z dolną granicą . T
jest najniższą klasą w hierarchii dziedziczenia. Użyj super
wieloznaczny jeśli tylko umieścić wartości w strukturze.Uwaga: symbol wieloznaczny ?
oznacza zero lub jeden raz , reprezentuje nieznany typ. Symbol wieloznaczny może być użyty jako typ parametru, nigdy nie może być użyty jako argument typu dla wywołania metody ogólnej, tworzenia instancji klasy ogólnej (tj. Gdy używany jest symbol wieloznaczny, który nie jest używany w innym miejscu programu, jak my 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.
Nie mogę dodać elementu do Listy <?> Lub Listy <? rozszerza Object>, więc nie rozumiem, dlaczego tak może być Use when you both get and put
.
?
- „niezwiązany symbol wieloznaczny” - odpowiada dokładnie odwrotności niezmienniczości. Proszę zapoznać się z następującą dokumentacją: docs.oracle.com/javase/tutorial/java/generics/..., która stwierdza: W przypadku, gdy kod musi mieć dostęp do zmiennej jako zmiennej „ wejściowej ” i „ wyjściowej ”, wykonaj nie używaj znaku wieloznacznego. (Używają „wejściowego” i „wyjściowego” jako synonimu „get” i „put”). Z wyjątkiem null
ciebie nie możesz dodać do kolekcji sparametryzowanej za pomocą ?
.
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
oznacza B i cokolwiek rozszerzającego B.
Jak wyjaśniam w mojej odpowiedzi na inne pytanie, PECS jest pamięciowy urządzenie stworzone przez Josh Bloch do pomocy pamiętać P roducer extends
, C onsumer super
.
Oznacza to, że gdy parametryczny typ przekazywany do metody wytworzy instancje
T
(zostaną w jakiś sposób z niej odzyskane),? extends T
należy użyć, ponieważ każda instancja podklasyT
jest równieżT
.Gdy typ parametryzowane są przekazywane do metody będą konsumować wystąpień
T
(zostaną one przekazane do niego coś zrobić),? super T
powinny być stosowane, ponieważ instancjaT
może być prawnie przekazane do dowolnej metody, które akceptuje supertypem z pewnąT
.Comparator<Number>
Może być stosowany naCollection<Integer>
, na przykład.? extends T
nie działałby, ponieważComparator<Integer>
nie mógł działać naCollection<Number>
.
Zauważ, że ogólnie powinieneś używać ? extends T
i ? super T
dla parametrów niektórych metod. Metody powinny po prostu służyć T
jako parametr typu w ogólnym typie zwracanym.
Krótko mówiąc, trzy łatwe do zapamiętania zasady PECS:
<? extends T>
symbolu wieloznacznego, jeśli chcesz pobrać obiekt typu T
z kolekcji.<? super T>
symbolu wieloznacznego, jeśli chcesz umieścić obiekty typu T
w kolekcji.przyjmijmy następującą hierarchię:
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
Wyjaśnijmy PE - producent rozszerza:
List<? extends Shark> sharks = new ArrayList<>();
Dlaczego nie można dodawać obiektów, które rozszerzają „Shark” na tej liście? lubić:
sharks.add(new HammerShark());//will result in compilation error
Ponieważ masz listę, która może być typu A, B lub C w czasie wykonywania , nie możesz dodać do niej żadnego obiektu typu A, B lub C, ponieważ możesz uzyskać kombinację, która nie jest dozwolona w Javie.
W praktyce kompilator rzeczywiście może zobaczyć w czasie kompilacji, że dodajesz B:
sharks.add(new HammerShark());
... ale nie ma sposobu na określenie, czy w czasie wykonywania Twój B będzie podtypem lub nadtypem typu listy. W czasie wykonywania typem listy może być dowolny z typów A, B, C. Dlatego nie można na przykład dodać HammerSkark (supertyp) na przykład do listy DeadHammerShark.
* Powiesz: „OK, ale dlaczego nie mogę dodać do niego HammerSkark, skoro jest to najmniejszy typ?”. Odpowiedź: Jest to najmniejszy, jaki znasz. Ale HammerSkark może być również przedłużony przez kogoś innego i kończysz w tym samym scenariuszu.
Wyjaśnijmy CS - Consumer Super:
W tej samej hierarchii możemy spróbować tego:
List<? super Shark> sharks = new ArrayList<>();
Co i dlaczego można dodać do tej listy?
sharks.add(new Shark());
sharks.add(new DeadHammerShark());
sharks.add(new HammerSkark());
Możesz dodać powyższe typy obiektów, ponieważ wszystko poniżej rekina (A, B, C) zawsze będzie podtypem czegoś powyżej rekina (X, Y, Z). Łatwy do zrozumienia.
Nie można dodawać typów powyżej Shark, ponieważ w czasie wykonywania typ dodanego obiektu może być wyższy w hierarchii niż deklarowany typ listy (X, Y, Z). To jest niedozwolone.
Ale dlaczego nie możesz czytać z tej listy? (Mam na myśli, że możesz wyciągnąć z niego element, ale nie możesz przypisać go do niczego innego niż Obiekt o):
Object o;
o = sharks.get(2);// only assignment that works
Animal s;
s = sharks.get(2);//doen't work
W czasie wykonywania typ listy może być dowolnego typu powyżej A: X, Y, Z, ... Kompilator może skompilować instrukcję przypisania (która wydaje się poprawna), ale w czasie wykonywania typ s (Zwierzę) może być niższy w hierarchia niż deklarowany typ listy (którym może być Creature lub wyższy). To jest niedozwolone.
Podsumowując
Używamy <? super T>
do dodawania obiektów typów równych lub niższych T
do List
. Nie możemy z tego odczytać.
Używamy <? extends T>
do odczytu obiektów typów równych lub niższych T
z listy. Nie możemy do tego dodać elementu.
(dodając odpowiedź, ponieważ nigdy zbyt mało przykładów z symbolami wieloznacznymi 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);
}
}
Jest to dla mnie najczystszy i najprostszy sposób na myślenie rozszerzeń vs. super:
extends
jest do czytania
super
jest do pisania
Uważam, że „PECS” to nieoczywisty sposób myślenia o tym, kto jest „producentem”, a kto „konsumentem”. „PECS” jest definiowany z perspektywy samego zbioru danych - zbiór „zużywa”, jeśli obiekty są do niego zapisywane (zużywa obiekty z kodu wywołującego), i „produkuje”, jeśli obiekty są z niego odczytywane (to produkuje obiekty do jakiegoś kodu wywołującego). Jest to jednak sprzeczne z nazwą wszystkiego innego. Standardowe interfejsy API Java są nazywane z perspektywy kodu wywołującego, a nie samej kolekcji. Na przykład widok java.util.List zorientowany na kolekcję powinien mieć metodę o nazwie „receive ()” zamiast „add ()” - w końcuelement, ale sama lista otrzymuje element.
Myślę, że bardziej intuicyjne, naturalne i spójne jest myślenie o rzeczach z perspektywy kodu, który wchodzi w interakcję z kolekcją - czy kod „czyta z” czy „pisze do” kolekcji? Następnie każdy kod zapisujący do kolekcji byłby „producentem”, a każdy odczyt kodu z kolekcji byłby „konsumentem”.
src
i dst
. Więc masz do czynienia zarówno z kodem, jak i kontenerami w tym samym czasie, a ja pomyślałem o tym w ten sposób - „konsumujący kod” konsumuje z produkującego kontenera, a „produkujący kod” produkuje dla konsumującego kontenera.
„Reguła” PECS zapewnia jedynie, że następujące elementy są zgodne z prawem:
?
to jest, może zgodnie z prawem się odnosić T
?
to jest, może być prawnie wskazane przez T
Typowe parowanie według linii List<? extends T> producer, List<? super T> consumer
polega po prostu na zapewnieniu, że kompilator może egzekwować standardowe reguły relacji dziedziczenia „IS-A”. Jeśli moglibyśmy to zrobić zgodnie z prawem, może być łatwiej powiedzieć <T extends ?>, <? extends T>
(lub jeszcze lepiej w Scali, jak widać powyżej, to jest [-T], [+T]
. Niestety, najlepsze, co możemy zrobić, to <? super T>, <? extends T>
.
Kiedy po raz pierwszy zetknąłem się z tym i zepsułem w głowie, mechanika miała sens, ale sam kod nadal wydawał mi się mylący - ciągle myślałem „wydaje się, że granice nie powinny być tak odwrócone” - mimo że ja było jasne w powyższym - że chodzi po prostu o zapewnienie zgodności ze standardowymi zasadami odniesienia.
Pomogło mi spojrzeć na to za pomocą zwykłego zadania jako analogii.
Rozważ następujący (niegotowy do produkcji) kod zabawki:
// 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);
}
Ilustrując to w kategoriach analogii przypisania do consumer
tej ?
asterisk (nieznany typ) jest odwołanie - w „lewa strona” o przelewie - i <? super T>
zapewnia, że bez względu na ?
to, T
„IS-A” ?
- które T
mogą być przypisane do niego, ponieważ ?
jest super typem (lub co najwyżej tego samego typu) co T
.
Dla producer
koncernu jest taka sama, to jest po prostu odwrócone: producer
„s ?
wieloznaczny (nieznany typ) jest referent -«prawo strony»przypisania - i <? extends T>
zapewnia, że bez względu na ?
to, ?
«IS-A» T
- że to może być przypisany do AT
, ponieważ ?
jest podtypem (lub przynajmniej takim samym typem) jak T
.
Na przykładzie z życia (z pewnymi uproszczeniami):
<? super FreightCarSize>
<? extends DepotSize>
Kowariancja : akceptuj podtypy
Kontrawariancja : akceptuj
Typy kowariantne są tylko do odczytu, natomiast typy przeciwstawne są tylko do odczytu.
Spójrzmy na przykład
public class A { }
//B is A
public class B extends A { }
//C is A
public class C extends A { }
Generics pozwala na dynamiczną pracę z typami w bezpieczny sposób
//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);
Ponieważ kolekcja Java jest typem referencyjnym, mamy kolejne problemy:
Problem nr 1
//not compiled
//danger of **adding** non-B objects using listA reference
listA = listB;
* Rodzajowy Swift nie ma takiego problemu, ponieważ Collection to Value type
[About], dlatego tworzona jest nowa kolekcja
Problem nr 2
//not compiled
//danger of **getting** non-B objects using listB reference
listB = listA;
Symbol wieloznaczny jest funkcją typu odniesienia i nie można go bezpośrednio utworzyć
Rozwiązanie nr 1,
<? super A>
czyli dolna granica, czyli kontrawariancja, czyli konsumenci, gwarantuje, że jest obsługiwana przez A i wszystkie nadklasy, dlatego można bezpiecznie dodawać
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);
Rozwiązanie nr 2
<? extends A>
aka górna granica aka kowariancja aka producenci gwarantuje, że jest ona obsługiwana przez A i wszystkie podklasy, dlatego można bezpiecznie uzyskać i obsadzić
List<? extends A> listExtendsA;
listExtendsA = listA;
listExtendsA = listB;
//get
A a0 = listExtendsA.get(0);
super
część, ale daje wyobrażenie o innym.