Bardzo dobre wyjaśnienie z przykładem @ youtube.com/watch?v=34oiEq9nD0M&feature=youtu.be&t=1630, który wyjaśnia superczęść, ale daje wyobrażenie o innym.
lupchiazoem
Odpowiedzi:
842
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 extendsalbo 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 Thingpodczas 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 Thingkolekcji 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ć Thingniezależnie od tego, jaki jest faktycznie sparametryzowany typ. Tutaj nie obchodzi Cię, co jest już na liście, o ile pozwoli to Thingna dodanie; to ? super Thinggwarantuje.
Zawsze staram się myśleć o tym w ten sposób: a producent może produkować coś bardziej specyficzne, stąd rozciąga , A konsument może przyjąć coś bardziej ogólnego, stąd bardzo .
Feuermurmel,
10
Innym sposobem zapamiętania rozróżnienia między producentem a konsumentem jest wymyślenie podpisu metody. Jeśli masz metodę 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ę).
Raman
3
@MichaelMyers: Dlaczego nie możemy po prostu użyć sparametryzowanego typu dla obu tych przypadków? Czy jest jakaś konkretna zaleta używania symboli wieloznacznych tutaj, czy jest to tylko sposób na poprawę czytelności podobnej do, powiedzmy, użycia odniesień constjako parametrów metody w C ++ w celu oznaczenia, że metoda nie modyfikuje argumentów?
Chatterjee
7
@Raman, myślę, że właśnie to pomyliłeś. W doSthWithList (możesz mieć List <? Super Thing>), ponieważ jesteś konsumentem, możesz używać super (pamiętaj, CS). Jest to jednak Lista <? rozszerza Thing> getList (), ponieważ możesz zwrócić coś bardziej szczegółowego podczas tworzenia (PE).
masterxilo
4
@AZ_ Podzielam twoje zdanie. Jeśli metoda pobiera () z listy, metoda zostanie uznana za konsumenta <T>, a lista zostanie uznana za dostawcę; ale reguła PECS jest „z punktu widzenia listy”, dlatego wymagane jest „rozszerzenie”. Powinien to być GEPS: get extends; umieścić super.
Treefish Zhang
561
Nazywa się to zasadami informatyki
Kowariancji: ? extends MyClass,
Kontrawariancja: ? super MyClassi
Niezmienność / brak wariancji: MyClass
Poniższy obrazek powinien wyjaśnić tę koncepcję. Zdjęcie dzięki uprzejmości: Andrey Tyukin
Hej wszystkim. Nazywam się Andrey Tyukin, chciałem tylko potwierdzić, że anoopelias i DaoWen skontaktowali się ze mną i otrzymali zgodę na korzystanie ze szkicu, jest on licencjonowany na podstawie (CC) -BY-SA Dzięki @ Anoop za drugie życie ^^ @Brian Agnew: (na „kilka głosów”): To dlatego, że jest szkicem dla Scali, używa składni Scali i zakłada wariancję strony deklaracji, która jest zupełnie inna niż dziwne wezwanie Javy wariancja witryny ... Może powinienem napisać bardziej szczegółową odpowiedź, która jasno pokazuje, jak ten szkic odnosi się do Javy ...
Andrey Tyukin
3
To jedno z najprostszych i najjaśniejszych wyjaśnień kowariancji i kontrawariancji, jakie kiedykolwiek znalazłem!
cs4r
@Andrey Tyukin Cześć, chcę również użyć tego obrazu. Jak mogę się z tobą skontaktować?
Użyj symbolu wieloznacznego rozszerzenia, gdy tylko pobierasz wartości ze struktury.
Użyj super symboli wieloznacznych, gdy wstawiasz tylko wartości do struktury.
I nie używaj symboli wieloznacznych, gdy oboje dostajesz i wkładasz.
Przykład w Javie:
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}
Typy danych (źródła) tylko do odczytu mogą być kowariantne ;
typy danych tylko do zapisu (ujścia) mogą być sprzeczne .
Zmienne typy danych, które działają zarówno jako źródła, jak i ujścia, powinny być niezmienne .
Aby zilustrować to ogólne zjawisko, rozważ typ tablicy. Dla typu Animal możemy utworzyć typ Animal []
kowariant : kot [] to zwierzę [];
kontrowariant : zwierzę [] to kot [];
niezmienny : zwierzę [] nie jest kotem [], a kot [] nie jest zwierzęciem [].
Przykłady 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
ograniczona (tj. zmierzająca gdzieś) symbol wieloznaczny : Istnieją 3 różne smaki symboli wieloznacznych:
In-variance / Non-variance: ?lub ? extends Object- Unbounded Wildcard. Oznacza rodzinę wszystkich typów. Użyj, gdy oboje weźmiesz i umieścisz.
Ko-wariancja: ? extends T(rodzina wszystkich typów, które są podtypami T) - symbol wieloznaczny z górną granicą . Tjest najwyższą klasą w hierarchii dziedziczenia. Użyj extendssymbolu wieloznacznego, gdy tylko pobierasz wartości ze struktury.
Kontrast wariancja: ? super T(rodzina wszystkich typów, które są nadtypami T) - symbol wieloznaczny z dolną granicą . Tjest najniższą klasą w hierarchii dziedziczenia. Użyj superwieloznaczny 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)
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.
*/}}
Hej, chciałem tylko wiedzieć, co miałeś na myśli z ostatnim sentymentem: „Jeśli uważasz, że moja analogia jest błędna, zaktualizuj”. Czy masz na myśli, czy jest to etycznie złe (co jest subiektywne) lub jeśli jest złe w kontekście programowania (które jest obiektywne: nie, to nie jest złe)? Chciałbym zastąpić go bardziej neutralnym przykładem, który jest ogólnie akceptowalny niezależnie od norm kulturowych i przekonań etycznych; Jeśli nie masz nic przeciwko.
Neuron
w końcu mogłem to zdobyć. Ładne wyjaśnienie.
Oleg Kuts
2
@Premraj, 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.
LiuWenbin_NO.
1
@LiuWenbin_NO. - Ta część odpowiedzi jest myląca. ?- „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 nullciebie nie możesz dodać do kolekcji sparametryzowanej za pomocą ?.
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}}
Zatem „? Rozszerza B” należy interpretować jako „? Przedłużenie B”. Jest to coś, co B rozszerza, aby obejmowało wszystkie superklasy B aż do Object, z wyłączeniem samego B. Dzięki za kod!
Saurabh Patil
3
@SaurabhPatil Nie, ? extends Boznacza B i cokolwiek rozszerzającego B.
asgs 28.09.16
24
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 Tnależy użyć, ponieważ każda instancja podklasy Tjest również T.
Gdy typ parametryzowane są przekazywane do metody będą konsumować wystąpień T(zostaną one przekazane do niego coś zrobić), ? super Tpowinny być stosowane, ponieważ instancja Tmoże być prawnie przekazane do dowolnej metody, które akceptuje supertypem z pewną T. Comparator<Number>Może być stosowany na Collection<Integer>, na przykład. ? extends Tnie działałby, ponieważ Comparator<Integer>nie mógł działać na Collection<Number>.
Zauważ, że ogólnie powinieneś używać ? extends Ti ? super Tdla parametrów niektórych metod. Metody powinny po prostu służyć Tjako parametr typu w ogólnym typie zwracanym.
Czy ta zasada obowiązuje tylko w przypadku kolekcji? Ma to sens, gdy próbuje się skorelować z listą. Jeśli myślisz o podpisie sortowania (Lista <T>, Komparator <? Super T>) ---> tutaj Komparator używa super, co oznacza, że jest konsumentem w kontekście PECS. Gdy spojrzysz na implementację, na przykład: public int porównaj (Osoba a, Osoba b) {return a.age <b.age? -1: a.age == b.age? 0: 1; } Wydaje mi się, że Osoba nie konsumuje niczego, ale produkuje wiek. To mnie zmieszało. Czy w moim rozumowaniu jest jakaś wada, czy PECS dotyczy tylko kolekcji?
Fatih Arslan,
23
Krótko mówiąc, trzy łatwe do zapamiętania zasady PECS:
Użyj <? extends T>symbolu wieloznacznego, jeśli chcesz pobrać obiekt typu Tz kolekcji.
Użyj <? super T>symbolu wieloznacznego, jeśli chcesz umieścić obiekty typu Tw kolekcji.
Jeśli musisz spełnić oba te warunki, nie używaj symboli wieloznacznych. Tak proste jak to.
classCreature{}// XclassAnimalextendsCreature{}// YclassFishextendsAnimal{}// ZclassSharkextendsFish{}// AclassHammerSkarkextendsShark{}// BclassDeadHammerSharkextendsHammerSkark{}// C
Wyjaśnijmy PE - producent rozszerza:
List<?extendsShark> sharks =newArrayList<>();
Dlaczego nie można dodawać obiektów, które rozszerzają „Shark” na tej liście? lubić:
sharks.add(newHammerShark());//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(newHammerShark());
... 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.
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 worksAnimal 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 Tdo List. Nie możemy z tego odczytać. Używamy <? extends T>do odczytu obiektów typów równych lub niższych Tz 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);// 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);}}
Jest to dla mnie najczystszy i najprostszy sposób na myślenie rozszerzeń vs. super:
extendsjest do czytania
superjest 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”.
Natknąłem się na tę samą kolizję umysłową i raczej się zgadzam, z wyjątkiem tego, że PECS nie określa nazwy kodu, a same granice typów są określone w deklaracjach kolekcji. Ponadto, jeśli chodzi o nazewnictwo, często masz nazwy do produkcji / konsumpcji kolekcji takich jak srci 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.
mouselabs
4
„Reguła” PECS zapewnia jedynie, że następujące elementy są zgodne z prawem:
Konsument: cokolwiek ?to jest, może zgodnie z prawem się odnosićT
Producent: cokolwiek ?to jest, może być prawnie wskazane przezT
Typowe parowanie według linii List<? extends T> producer, List<? super T> consumerpolega 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 consumertej ?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 Tmogą być przypisane do niego, ponieważ ?jest super typem (lub co najwyżej tego samego typu) co T.
Dla producerkoncernu 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.
publicclass A {}//B is Apublicclass B extends A {}//C is Apublicclass C extends A {}
Generics pozwala na dynamiczną pracę z typami w bezpieczny sposób
//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);
Problem
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;
Rozwiązanie - ogólne symbole wieloznaczne
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ć
<? 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.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ć alboextends
albosuper
.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 podtypThing
, a zatem każdy element będzie zachowywał się jakThing
podczas wykonywania operacji. (Tak naprawdę nie możesz nic dodać doCollection<? extends Thing>
, ponieważ nie możesz wiedzieć w czasie wykonywania, który konkretny podtypThing
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 toThing
na dodanie; to? super Thing
gwarantuje.źródło
doSomethingWithList(List list)
, zużywasz listę, więc będziesz potrzebować kowariancji / rozszerzeń (lub niezmiennej listy). Z drugiej strony, jeśli jest metodaList 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
źródło
PECS (producent
extends
i konsumentsuper
)mnemonic → Zasada Get and Put.
Zasada ta stanowi, że:
Przykład w Javie:
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
Kowariancja i kontrawariancja
Aby zilustrować to ogólne zjawisko, rozważ typ tablicy. Dla typu Animal możemy utworzyć typ Animal []
Przykłady Java:
więcej przykładów
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ą podtypamiT
) - symbol wieloznaczny z górną granicą .T
jest najwyższą klasą w hierarchii dziedziczenia. Użyjextends
symbolu wieloznacznego, gdy tylko pobierasz wartości ze struktury.? super T
(rodzina wszystkich typów, które są nadtypamiT
) - symbol wieloznaczny z dolną granicą .T
jest najniższą klasą w hierarchii dziedziczenia. Użyjsuper
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 myT
)ogólne i przykłady
źródło
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ątkiemnull
ciebie nie możesz dodać do kolekcji sparametryzowanej za pomocą?
.źródło
? 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 onsumersuper
.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.źródło
Krótko mówiąc, trzy łatwe do zapamiętania zasady PECS:
<? extends T>
symbolu wieloznacznego, jeśli chcesz pobrać obiekt typuT
z kolekcji.<? super T>
symbolu wieloznacznego, jeśli chcesz umieścić obiekty typuT
w kolekcji.źródło
przyjmijmy następującą hierarchię:
Wyjaśnijmy PE - producent rozszerza:
Dlaczego nie można dodawać obiektów, które rozszerzają „Shark” na tej liście? lubić:
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:
... 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:
Co i dlaczego można dodać do tej listy?
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):
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ższychT
doList
. Nie możemy z tego odczytać.Używamy
<? extends T>
do odczytu obiektów typów równych lub niższychT
z listy. Nie możemy do tego dodać elementu.źródło
(dodając odpowiedź, ponieważ nigdy zbyt mało przykładów z symbolami wieloznacznymi Generics)
źródło
Jest to dla mnie najczystszy i najprostszy sposób na myślenie rozszerzeń vs. super:
extends
jest do czytaniasuper
jest do pisaniaUważ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”.
źródło
src
idst
. 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 przezT
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:
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óreT
mogą być przypisane do niego, ponieważ?
jest super typem (lub co najwyżej tego samego typu) coT
.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) jakT
.źródło
Pamiętaj to:
źródło
Na przykładzie z życia (z pewnymi uproszczeniami):
<? super FreightCarSize>
<? extends DepotSize>
źródło
Kowariancja : akceptuj podtypy
Kontrawariancja : akceptuj
Typy kowariantne są tylko do odczytu, natomiast typy przeciwstawne są tylko do odczytu.
źródło
Spójrzmy na przykład
Generics pozwala na dynamiczną pracę z typami w bezpieczny sposób
Problem
Ponieważ kolekcja Java jest typem referencyjnym, mamy kolejne problemy:
Problem nr 1
* Rodzajowy Swift nie ma takiego problemu, ponieważ Collection to
Value type
[About], dlatego tworzona jest nowa kolekcjaProblem nr 2
Rozwiązanie - ogólne symbole wieloznaczne
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ć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ćźródło