Co to jest PECS (producent rozszerza super konsumenta)?

729

Natknąłem się na PECS (skrót od producenta extendsi konsumentasuper ), czytając ogólne informacje.

Czy ktoś może mi wyjaśnić, jak korzystać z PECS do rozwiązywania konfliktu między extendsa super?

szczyt
źródło
3
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.

Michael Myers
źródło
142
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

Kowariancja kontra kontrawariancja

anoopelias
źródło
143
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ć?
slouc
Jeśli masz jakieś pytania dotyczące tej ilustracji, możemy omówić je na czacie: chat.stackoverflow.com/rooms/145734/...
Andrey Tyukin
48

PECS (producent extendsi konsument super)

mnemonic → Zasada Get and Put.

Zasada ta stanowi, że:

  • 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:

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

  • kowariant, jeśli zachowuje uporządkowanie typów (≤), która porządkuje typy od bardziej szczegółowych do bardziej ogólnych;
  • sprzeczne, jeśli odwraca to uporządkowanie;
  • niezmienny lub niezmienny, jeśli żadna z tych nie ma zastosowania.

Kowariancja i kontrawariancja

  • 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= 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  

więcej przykładów

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)

wprowadź opis zdjęcia tutaj

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.
        */  
    }
}

ogólne i przykłady

Premraj
źródło
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
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
    }
}
Gadanina
źródło
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.

ColinD
źródło
1
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:

  1. Użyj <? extends T>symbolu wieloznacznego, jeśli chcesz pobrać obiekt typu Tz kolekcji.
  2. Użyj <? super T>symbolu wieloznacznego, jeśli chcesz umieścić obiekty typu Tw kolekcji.
  3. Jeśli musisz spełnić oba te warunki, nie używaj symboli wieloznacznych. Tak proste jak to.
Pradeep Kr Kaushal
źródło
10

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 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.

Daniel
źródło
9

(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);
        }
    }
Andrejs
źródło
4

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”.

kaan
źródło
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 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 przez T

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.

myszki
źródło
2

Pamiętaj to:

Konsumenci jedzą kolację (super); Producent rozbudowuje fabrykę swojego rodzica

Jason
źródło
1

Na przykładzie z życia (z pewnymi uproszczeniami):

  1. Wyobraź sobie pociąg towarowy z wagonami towarowymi jako analogię do listy.
  2. Możesz włożyć ładunek do wagonu towarowego, jeśli ładunek ma taki sam lub mniejszy rozmiar niż wagony towarowe =<? super FreightCarSize>
  3. Możesz rozładować ładunek z wagonu towarowego, jeśli masz wystarczająco dużo miejsca (więcej niż wielkość ładunku) w magazynie =<? extends DepotSize>
contrapost
źródło
1

Kowariancja : akceptuj podtypy
Kontrawariancja : akceptuj

Typy kowariantne są tylko do odczytu, natomiast typy przeciwstawne są tylko do odczytu.

Farrukh Chishti
źródło
0

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);

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ć

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);

yoAlex5
źródło