Was ist PECS (Producer Extends Consumer Super)?

729

Ich bin auf PECS gestoßen (kurz für Producer extendsand Consumer)super ) .

Kann mir jemand erklären, wie man PECS verwendet, um Verwirrung zwischen extendsund zu lösen super?

Peakit
quelle
3
Eine sehr gute Erklärung mit einem Beispiel @ youtube.com/watch?v=34oiEq9nD0M&feature=youtu.be&t=1630, das einen superTeil erklärt, aber eine Vorstellung von einem anderen gibt.
Lupchiazoem

Antworten:

843

tl; dr: "PECS" ist aus Sicht der Sammlung. Wenn Sie nur Artikel aus einer generischen Sammlung ziehen, handelt es sich um einen Hersteller, den Sie verwenden sollten extends. Wenn Sie nur Artikel einfüllen, handelt es sich um einen Verbraucher, den Sie verwenden sollten super. Wenn Sie beide mit derselben Sammlung ausführen, sollten Sie weder extendsoder verwenden super.


Angenommen, Sie haben eine Methode, die eine Sammlung von Dingen als Parameter verwendet, möchten jedoch, dass sie flexibler ist als nur eine zu akzeptieren Collection<Thing>.

Fall 1: Sie möchten die Sammlung durchgehen und mit jedem Element Dinge tun.
Dann ist die Liste ein Produzent , also sollten Sie a verwenden Collection<? extends Thing>.

Der Grund dafür ist, dass a einen Collection<? extends Thing>beliebigen Subtyp von enthalten Thingkann und sich daher jedes Element wie ein verhält, Thingwenn Sie Ihre Operation ausführen. (Sie können a tatsächlich nichts hinzufügen Collection<? extends Thing>, da Sie zur Laufzeit nicht wissen können, welcher bestimmte Subtyp Thingder Sammlung enthält.)

Fall 2: Sie möchten der Sammlung Dinge hinzufügen.
Dann ist die Liste ein Verbraucher , also sollten Sie a verwendenCollection<? super Thing> .

Der Grund hierfür ist, dass im Gegensatz zu Collection<? extends Thing>, Collection<? super Thing>kann immer ein halten, Thingegal was der tatsächliche parametrisierte Typ ist. Hier ist es Ihnen egal, was bereits in der Liste enthalten ist, solange ein ThingHinzufügen hinzugefügt werden kann. das ? super Thinggarantiert.

Michael Myers
quelle
142
Ich versuche immer, so darüber nachzudenken: Ein Produzent darf etwas Spezifischeres produzieren, also erweitert , ein Verbraucher darf etwas Allgemeineres akzeptieren, also Super .
Feuermurmel
10
Eine andere Möglichkeit, sich an die Unterscheidung zwischen Hersteller und Verbraucher zu erinnern, besteht darin, sich eine Methodensignatur vorzustellen. Wenn Sie eine Methode haben doSomethingWithList(List list), verbrauchen Sie die Liste und benötigen daher Kovarianz / Erweiterung (oder eine invariante Liste). Auf der anderen Seite, wenn Ihre Methode ist List doSomethingProvidingList, dann erstellen Sie die Liste und benötigen Kontravarianz / Super (oder eine invariante Liste).
Raman
3
@MichaelMyers: Warum können wir für beide Fälle nicht einfach einen parametrisierten Typ verwenden? Gibt es hier einen besonderen Vorteil der Verwendung von Platzhaltern oder ist dies nur ein Mittel zur Verbesserung der Lesbarkeit, ähnlich wie beispielsweise die Verwendung von Verweisen auf constals Methodenparameter in C ++, um anzuzeigen, dass die Methode die Argumente nicht ändert?
Chatterjee
7
@ Raman, ich denke du hast es nur verwirrt. In doSthWithList (Sie können List <? Super Thing> haben) können Sie Super verwenden (denken Sie daran, CS), da Sie ein Verbraucher sind. Es ist jedoch Liste <? erweitert Thing> getList (), da Sie beim Produzieren (PE) etwas Spezifischeres zurückgeben dürfen.
Masterxilo
4
@AZ_ Ich teile dein Gefühl. Wenn eine Methode get () aus der Liste erhält, wird die Methode als Consumer <T> und die Liste als Provider betrachtet. Die Regel von PECS lautet jedoch "aus Sicht der Liste", daher ist "Erweitern" erforderlich. Es sollte GEPS sein: get erweitert; super setzen.
Treefish Zhang
561

Die Prinzipien dahinter in der Informatik heißen

  • Kovarianz : ? extends MyClass,
  • Kontravarianz: ? super MyClassund
  • Invarianz / Nichtvarianz: MyClass

Das Bild unten sollte das Konzept erklären. Mit freundlicher Genehmigung von Andrey Tyukin

Kovarianz gegen Kontravarianz

Anoopelien
quelle
143
Hallo alle miteinander. Ich bin Andrey Tyukin. Ich wollte nur bestätigen, dass anoopelias & DaoWen mich kontaktiert und meine Erlaubnis zur Verwendung der Skizze erhalten haben. Sie ist unter (CC) -BY-SA lizenziert. Thx @ Anoop für das zweite Leben ^^ @Brian Agnew: (bei "wenigen Stimmen"): Das liegt daran, dass es sich um eine Skizze für Scala handelt, die Scala-Syntax verwendet und eine Abweichung von der Deklarationsstelle annimmt, die sich deutlich von Javas seltsamem Aufruf unterscheidet -site Varianz ... Vielleicht sollte ich eine detailliertere Antwort schreiben, die deutlich zeigt, wie diese Skizze für Java gilt ...
Andrey Tyukin
3
Dies ist eine der einfachsten und klarsten Erklärungen für Kovarianz und Kontravarianz, die ich je gefunden habe!
CS4R
@Andrey Tyukin Hallo, ich möchte auch dieses Bild verwenden. Wie kann ich dich erreichen?
Slouc
Wenn Sie Fragen zu dieser Abbildung haben, können wir diese im Chatroom besprechen: chat.stackoverflow.com/rooms/145734/…
Andrey Tyukin
48

PECS (Produzent extendsund Konsument super)

Mnemonik → Get and Put-Prinzip.

Dieses Prinzip besagt:

  • Verwenden Sie einen erweiterten Platzhalter, wenn Sie nur Werte aus einer Struktur erhalten.
  • Verwenden Sie einen Super-Platzhalter, wenn Sie nur Werte in eine Struktur einfügen.
  • Und verwenden Sie keinen Platzhalter, wenn Sie beide erhalten und setzen.

Beispiel in Java:

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

}

Liskov-Substitutionsprinzip: Wenn S ein Subtyp von T ist, können Objekte vom Typ T durch Objekte vom Typ S ersetzt werden.

Innerhalb des Typsystems einer Programmiersprache eine Tippregel

  • kovariant, wenn die Reihenfolge der Typen (≤) beibehalten wird, wodurch die Typen von spezifischer zu allgemeiner geordnet werden;
  • kontravariant, wenn es diese Reihenfolge umkehrt;
  • invariant oder nicht variant, wenn keines davon zutrifft.

Kovarianz und Kontravarianz

  • Schreibgeschützte Datentypen (Quellen) können kovariant sein .
  • Nur-Schreib-Datentypen (Senken) können kontravariant sein .
  • Veränderbare Datentypen, die sowohl als Quellen als auch als Senken fungieren, sollten unveränderlich sein .

Betrachten Sie den Array-Typ, um dieses allgemeine Phänomen zu veranschaulichen. Für den Typ Tier können wir den Typ Tier [] machen

  • Kovariante : Eine Katze [] ist ein Tier [];
  • kontravariante : ein Tier [] ist eine Katze [];
  • Invariante : Ein Tier [] ist keine Katze [] und eine Katze [] ist kein Tier [].

Java-Beispiele:

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  

mehr Beispiele

Begrenzter Platzhalter (dh Richtung irgendwo hin) : Es gibt 3 verschiedene Arten von Platzhaltern:

  • Abweichung / Nichtabweichung: ?oder ? extends Object- Ungebunden Platzhalter. Es steht für die Familie aller Arten. Verwenden Sie, wenn Sie beide bekommen und setzen.
  • Co-Varianz: ? extends T(die Familie aller Typen, die Subtypen von sind T) - ein Platzhalter mit einer Obergrenze . Tist die oberste Klasse in der Vererbungshierarchie. Verwenden Sie einen extendsPlatzhalter , wenn Sie nur Holen Sie Werte aus einer Struktur.
  • Gegenvarianz: ? super T(die Familie aller Typen, die Supertypen sind T) - ein Platzhalter mit einer Untergrenze . Tist die unterste Klasse in der Vererbungshierarchie. Verwenden Sie einen superPlatzhalter , wenn Sie nur Put Werte in eine Struktur.

Hinweis: Platzhalter ?bedeutet null oder einmal , steht für einen unbekannten Typ. Der Platzhalter kann als Typ eines Parameter verwendet werden, niemals als eine Art Argument für einen generischen Methodenaufruf, eine generische Klasseninstanz Erstellung verwendet. (Dh wenn gebrauchter Wildcard dass Verweis in an anderer Stelle im Programm nicht verwendet , wie wir verwenden T)

Geben Sie hier die Bildbeschreibung ein

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

Generika und Beispiele

Premraj
quelle
Hey, ich wollte nur wissen, was du mit dem letzten Urteil gemeint hast: "Wenn du denkst, dass meine Analogie falsch ist, aktualisiere bitte". Meinen Sie, wenn es ethisch falsch ist (was subjektiv ist) oder wenn es im Kontext der Programmierung falsch ist (was objektiv ist: nein, es ist nicht falsch)? Ich möchte es durch ein neutraleres Beispiel ersetzen, das unabhängig von kulturellen Normen und ethischen Überzeugungen allgemein akzeptabel ist. Wenn das für dich in Ordnung ist.
Neuron
Endlich konnte ich es bekommen. Schöne Erklärung.
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.Ich kann kein Element zu List <?> Oder List <? erweitert Object>, daher verstehe ich nicht, warum es sein kann Use when you both get and put.
LiuWenbin_NO.
1
@LiuWenbin_NO. - Dieser Teil der Antwort ist irreführend. ?- die "unbegrenzte Wildcard" - entspricht dem genauen Gegenteil der Invarianz. Bitte beachten Sie die folgenden Dokumentation: docs.oracle.com/javase/tutorial/java/generics/... in dem es heißt: In dem Fall, dass die Code - Bedürfnisse sowohl die Variable für den Zugriff auf als „in“ und „out“ Variable, tut Verwenden Sie keinen Platzhalter. (Sie verwenden "in" und "out" als Synonym für "get" und "put"). Mit Ausnahme von können nullSie nicht zu einer mit parametrierten Sammlung hinzufügen ?.
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
    }
}
Gab
quelle
"? Erweitert B" sollte also als "? B erweitert" interpretiert werden. Es ist etwas, das B erweitert, so dass alle Superklassen von B bis zum Objekt eingeschlossen sind, mit Ausnahme von B selbst. Danke für den Code!
Saurabh Patil
3
@ SaurabhPatil Nein, ? extends Bbedeutet B und alles , was B erweitert.
Asgs
24

Wie ich in erklären meine Antwort auf eine andere Frage, ist PECS eine Gedächtnisstütze von Josh Bloch um Hilfe erinnern erstellt P roducer extends, C onsumer super.

Dies bedeutet, dass, wenn ein parametrisierter Typ, der an eine Methode übergeben wird , Instanzen von erzeugt T(diese werden auf irgendeine Weise daraus abgerufen), ? extends Tverwendet werden sollte, da jede Instanz einer Unterklasse von Tauch a ist T.

Wenn ein parametrisierter Typ, der an eine Methode übergeben wird , Instanzen von verbrauchtT (sie werden an sie übergeben, um etwas zu tun), ? super Tsollte verwendet werden, da eine Instanz von Tlegal an jede Methode übergeben werden kann, die einen Supertyp von akzeptiert T. A Comparator<Number>könnte Collection<Integer>zum Beispiel für a verwendet werden. ? extends Twürde nicht funktionieren, weil a Comparator<Integer>nicht an einem arbeiten konnte Collection<Number>.

Beachten Sie, dass Sie im Allgemeinen nur ? extends Tund ? super Tfür die Parameter einer Methode verwenden sollten. Methoden sollten nur Tals Typparameter für einen generischen Rückgabetyp verwendet werden.

ColinD
quelle
1
Gilt dieses Prinzip nur für Sammlungen? Es ist sinnvoll, wenn man versucht, es mit einer Liste zu korrelieren. Wenn Sie über die Signatur der Sortierung nachdenken (Liste <T>, Komparator <? Super T>) --->, verwendet der Komparator hier Super, was bedeutet, dass er ein Verbraucher im PECS-Kontext ist. Wenn Sie sich die Implementierung beispielsweise wie folgt ansehen: public int compare (Person a, Person b) {return a.age <b.age? -1: a.age == b.age? 0: 1; } Ich habe das Gefühl, dass Person nichts konsumiert, sondern Alter produziert. Das macht mich verwirrt. Gibt es einen Fehler in meiner Argumentation oder gilt PECS nur für Sammlungen?
Fatih Arslan
23

Kurz gesagt, drei einfache Regeln, an die Sie sich bei PECS erinnern sollten:

  1. Verwenden Sie den <? extends T>Platzhalter, wenn Sie ein Objekt vom Typ Taus einer Sammlung abrufen müssen .
  2. Verwenden Sie den <? super T>Platzhalter, wenn Sie Objekte vom Typ Tin eine Sammlung einfügen müssen.
  3. Wenn Sie beide Anforderungen erfüllen müssen, verwenden Sie keinen Platzhalter. So einfach ist das.
Pradeep Kr Kaushal
quelle
10

Nehmen wir diese Hierarchie an:

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

Lassen Sie uns PE - Producer Extends klarstellen:

List<? extends Shark> sharks = new ArrayList<>();

Warum können Sie dieser Liste keine Objekte hinzufügen, die "Shark" erweitern? mögen:

sharks.add(new HammerShark());//will result in compilation error

Da Sie eine Liste haben, die zur Laufzeit vom Typ A, B oder C sein kann , können Sie kein Objekt vom Typ A, B oder C hinzufügen, da Sie möglicherweise eine Kombination erhalten, die in Java nicht zulässig ist.
In der Praxis kann der Compiler tatsächlich zur Kompilierungszeit sehen, dass Sie ein B hinzufügen:

sharks.add(new HammerShark());

... aber es kann nicht festgestellt werden, ob Ihr B zur Laufzeit ein Subtyp oder Supertyp des Listentyps ist. Zur Laufzeit kann der Listentyp einer der Typen A, B, C sein. Sie können also beispielsweise HammerSkark (Supertyp) nicht in eine Liste von DeadHammerShark einfügen.

* Sie werden sagen: "OK, aber warum kann ich HammerSkark nicht hinzufügen, da es der kleinste Typ ist?". Antwort: Es ist das kleinste du kennen. Aber HammerSkark kann auch von jemand anderem erweitert werden und Sie landen im selben Szenario.

Lassen Sie uns CS - Consumer Super klarstellen:

In derselben Hierarchie können wir Folgendes versuchen:

List<? super Shark> sharks = new ArrayList<>();

Was und warum können Sie dieser Liste hinzufügen?

sharks.add(new Shark());
sharks.add(new DeadHammerShark());
sharks.add(new HammerSkark());

Sie können die oben genannten Objekttypen hinzufügen, da alles, was sich unter dem Hai (A, B, C) befindet, immer Untertypen von allem ist, was sich über dem Hai befindet (X, Y, Z). Einfach zu verstehen.

Sie können keine Typen über Shark hinzufügen, da zur Laufzeit der Typ des hinzugefügten Objekts in der Hierarchie höher sein kann als der deklarierte Typ der Liste (X, Y, Z). Das ist nicht erlaubt.

Aber warum können Sie nicht aus dieser Liste lesen? (Ich meine, Sie können ein Element daraus ziehen, aber Sie können es nichts anderem als Objekt o zuweisen.):

Object o;
o = sharks.get(2);// only assignment that works

Animal s;
s = sharks.get(2);//doen't work

Zur Laufzeit kann der Listentyp ein beliebiger Typ über A: X, Y, Z, ... sein. Der Compiler kann Ihre Zuweisungsanweisung (die korrekt erscheint) kompilieren, jedoch zur Laufzeit der Typ von s (Animal) niedriger sein Hierarchie als der deklarierte Typ der Liste (der Creature oder höher sein kann). Das ist nicht erlaubt.

Um zusammenzufassen

Wir verwenden <? super T>, um Objekte vom Typ gleich oder darunter Tzum hinzuzufügen List. Wir können nicht daraus lesen.
Wir verwenden <? extends T>, um Objekte vom Typ gleich oder niedriger Taus der Liste zu lesen . Wir können kein Element hinzufügen.

Daniel
quelle
9

(Hinzufügen einer Antwort, da nie genug Beispiele mit Generics-Platzhaltern vorhanden sind)

       // 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
quelle
4

Dies ist für mich der klarste und einfachste Weg, an Extend vs. Super zu denken:

  • extendsist zum Lesen

  • superist zum Schreiben

Ich finde, dass "PECS" eine nicht offensichtliche Art ist, Dinge darüber zu denken, wer der "Produzent" und wer der "Verbraucher" ist. „PECS“ wird aus der Perspektive der definierten Datensammlung selbst - die Sammlung „verbraucht“ , wenn Objekte geschrieben werden , um sie (es wird Objekte aus Telefonvorwahl raubend), und es „produziert“ , wenn Objekte gelesen werden von ihm (es erzeugt Objekte für einen aufrufenden Code). Dies widerspricht jedoch der Bezeichnung aller anderen Elemente. Standard-Java-APIs werden aus der Perspektive des aufrufenden Codes benannt, nicht der Sammlung selbst. Beispielsweise sollte eine sammlungszentrierte Ansicht von java.util.List eine Methode mit dem Namen "receive ()" anstelle von "add ()" haben - schließlichdas Element, aber die Liste selbstempfängt das Element.

Ich denke, es ist intuitiver, natürlicher und konsistenter, Dinge aus der Perspektive des Codes zu betrachten, der mit der Sammlung interagiert - liest oder schreibt der Code aus der Sammlung? Danach wäre jeder Code , der in die Sammlung schreibt, der "Produzent", und jeder Code , der aus der Sammlung liest, wäre der "Verbraucher".

Kaan
quelle
Ich habe in der gleichen psychischen Kollision laufen und würde außer eher zustimmen , dass PECS nicht die Benennung von Code angeben und die Art Grenzen selbst sind auf die Sammlung Erklärungen gesetzt. Darüber hinaus haben Sie in Bezug auf die Benennung häufig Namen für das Produzieren / Konsumieren von Sammlungen wie srcund dst. Sie beschäftigen sich also gleichzeitig mit Code und Containern, und am Ende habe ich darüber nachgedacht: "Konsumieren von Code" wird aus einem produzierenden Container verbraucht, und "Produzieren von Code" wird für einen konsumierenden Container erzeugt.
Mouselabs
4

Die PECS "Regel" stellt lediglich sicher, dass Folgendes legal ist:

  • Verbraucher: Was auch immer ?ist, es kann sich legal darauf beziehen T
  • Produzent: Was auch immer ?ist, es kann legal von bezeichnet werden T

Die typische Paarung nach dem Vorbild von List<? extends T> producer, List<? super T> consumer besteht lediglich darin, sicherzustellen, dass der Compiler die Standardregeln für die Vererbungsbeziehung "IS-A" durchsetzen kann. Wenn wir dies legal tun könnten, wäre es vielleicht einfacher zu sagen <T extends ?>, <? extends T>(oder besser noch in Scala, wie Sie oben sehen können [-T], [+T]. Leider ist das Beste, was wir tun können <? super T>, <? extends T>.

Als ich zum ersten Mal darauf stieß und es in meinem Kopf zerlegte, machte die Mechanik Sinn, aber der Code selbst sah für mich weiterhin verwirrend aus - ich dachte immer wieder: "Es scheint, als müssten die Grenzen nicht so umgekehrt werden müssen" - obwohl ich war klar, dass es einfach darum geht, die Einhaltung der Standard-Referenzregeln zu gewährleisten.

Was mir geholfen hat, war es, es mit einer gewöhnlichen Aufgabe als Analogie zu betrachten.

Beachten Sie den folgenden (nicht produktionsfertigen) Spielzeugcode:

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

Veranschaulichen dies in Bezug auf die Zuordnung Analogie für consumerden ?Platzhalter (unbekannter Typ) wird die Referenz - die „linke Seite“ der Zuordnung - und <? super T>sorgt dafür , dass alles , was ?ist, T„IS-A“ ?- , die Tihm zugeordnet werden können, weil ?ist ein Supertyp (oder höchstens der gleiche Typ) wie T.

Für producerdie Sorge ist die gleiche es gerade umgekehrt ist: producer‚s ?Platzhalter (unbekannter Typ) ist den referent - die‚rechte Seite‘der Zuordnung - und <? extends T>sorgt dafür , dass alles , was ?ist, ?‚IS-A‘ T- , dass es zugeordnet werden kann , um einT , weil ?es sich um einen Untertyp (oder zumindest denselben Typ) handelt wie T.

Mouselabs
quelle
2

Merk dir das:

Verbraucher essen Abendessen (super); Der Produzent erweitert die Fabrik seiner Eltern

Jason
quelle
1

Beispiel aus dem wirklichen Leben (mit einigen Vereinfachungen):

  1. Stellen Sie sich einen Güterzug mit Güterwagen als Analogie zu einer Liste vor.
  2. Sie können eine Ladung in einen Güterwagen legen , wenn die Ladung die gleiche oder eine kleinere Größe als der Güterwagen hat =<? super FreightCarSize>
  3. Sie können eine Ladung aus einem Güterwagen entladen , wenn Sie genügend Platz (mehr als die Größe der Ladung) in Ihrem Depot haben =<? extends DepotSize>
Contrapost
quelle
1

Kovarianz : Subtypen akzeptieren
Kontravarianz : Supertypen akzeptieren

Kovariante Typen sind schreibgeschützt, während kontravariante Typen schreibgeschützt sind.

Farrukh Chishti
quelle
0

Schauen wir uns ein Beispiel an

public class A { }
//B is A
public class B extends A { }
//C is A
public class C extends A { }

Mit Generics können Sie auf sichere Weise dynamisch mit Typen arbeiten

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

Da Javas Sammlung daher ein Referenztyp ist, haben wir folgende Probleme:

Problem Nr. 1

//not compiled
//danger of **adding** non-B objects using listA reference
listA = listB;

* Swifts Generikum hat kein solches Problem, da Collection Value type[About] ist und daher eine neue Collection erstellt wird

Problem Nr. 2

//not compiled
//danger of **getting** non-B objects using listB reference
listB = listA;

Die Lösung - Generische Platzhalter

Platzhalter sind ein Referenztyp-Feature und können nicht direkt instanziiert werden

Lösung Nr. 1, auch <? super A> bekannt als Untergrenze oder Kontravarianz oder Verbraucher, garantiert, dass sie von A und allen Superklassen betrieben wird. Deshalb ist es sicher, sie hinzuzufügen

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

Lösung Nr. 2

<? extends A>aka Obergrenze aka Kovarianz aka Produzenten garantiert, dass es von A und allen Unterklassen betrieben wird, deshalb ist es sicher zu bekommen und zu besetzen

List<? extends A> listExtendsA;
listExtendsA = listA;
listExtendsA = listB;

//get
A a0 = listExtendsA.get(0);

yoAlex5
quelle