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.
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
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?
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:
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}
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.
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=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
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)
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.
*/}}
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
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}}
"? 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.
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:
Verwenden Sie den <? extends T>Platzhalter, wenn Sie ein Objekt vom Typ Taus einer Sammlung abrufen müssen .
Verwenden Sie den <? super T>Platzhalter, wenn Sie Objekte vom Typ Tin eine Sammlung einfügen müssen.
Wenn Sie beide Anforderungen erfüllen müssen, verwenden Sie keinen Platzhalter. So einfach ist das.
classCreature{}// XclassAnimalextendsCreature{}// YclassFishextendsAnimal{}// ZclassSharkextendsFish{}// AclassHammerSkarkextendsShark{}// BclassDeadHammerSharkextendsHammerSkark{}// C
Lassen Sie uns PE - Producer Extends klarstellen:
List<?extendsShark> sharks =newArrayList<>();
Warum können Sie dieser Liste keine Objekte hinzufügen, die "Shark" erweitern? mögen:
sharks.add(newHammerShark());//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(newHammerShark());
... 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:
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 worksAnimal 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.
(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);// 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);}}
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".
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 beziehenT
Produzent: Was auch immer ?ist, es kann legal von bezeichnet werdenT
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.
Beispiel aus dem wirklichen Leben (mit einigen Vereinfachungen):
Stellen Sie sich einen Güterzug mit Güterwagen als Analogie zu einer Liste vor.
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>
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>
publicclass A {}//B is Apublicclass B extends A {}//C is Apublicclass C extends A {}
Mit Generics können Sie auf sichere Weise dynamisch mit Typen arbeiten
//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
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
<? 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);
super
Teil erklärt, aber eine Vorstellung von einem anderen gibt.Antworten:
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 solltensuper
. Wenn Sie beide mit derselben Sammlung ausführen, sollten Sie wederextends
oder verwendensuper
.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 enthaltenThing
kann und sich daher jedes Element wie ein verhält,Thing
wenn Sie Ihre Operation ausführen. (Sie können a tatsächlich nichts hinzufügenCollection<? extends Thing>
, da Sie zur Laufzeit nicht wissen können, welcher bestimmte SubtypThing
der Sammlung enthält.)Fall 2: Sie möchten der Sammlung Dinge hinzufügen.
Dann ist die Liste ein Verbraucher , also sollten Sie a verwenden
Collection<? super Thing>
.Der Grund hierfür ist, dass im Gegensatz zu
Collection<? extends Thing>
,Collection<? super Thing>
kann immer ein halten,Thing
egal was der tatsächliche parametrisierte Typ ist. Hier ist es Ihnen egal, was bereits in der Liste enthalten ist, solange einThing
Hinzufügen hinzugefügt werden kann. das? super Thing
garantiert.quelle
doSomethingWithList(List list)
, verbrauchen Sie die Liste und benötigen daher Kovarianz / Erweiterung (oder eine invariante Liste). Auf der anderen Seite, wenn Ihre Methode istList doSomethingProvidingList
, dann erstellen Sie die Liste und benötigen Kontravarianz / Super (oder eine invariante Liste).const
als Methodenparameter in C ++, um anzuzeigen, dass die Methode die Argumente nicht ändert?Die Prinzipien dahinter in der Informatik heißen
? extends MyClass
,? super MyClass
undMyClass
Das Bild unten sollte das Konzept erklären. Mit freundlicher Genehmigung von Andrey Tyukin
quelle
PECS (Produzent
extends
und Konsumentsuper
)Mnemonik → Get and Put-Prinzip.
Dieses Prinzip besagt:
Beispiel in Java:
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
Kovarianz und Kontravarianz
Betrachten Sie den Array-Typ, um dieses allgemeine Phänomen zu veranschaulichen. Für den Typ Tier können wir den Typ Tier [] machen
Java-Beispiele:
mehr Beispiele
Begrenzter Platzhalter (dh Richtung irgendwo hin) : Es gibt 3 verschiedene Arten von Platzhaltern:
?
oder? extends Object
- Ungebunden Platzhalter. Es steht für die Familie aller Arten. Verwenden Sie, wenn Sie beide bekommen und setzen.? extends T
(die Familie aller Typen, die Subtypen von sindT
) - ein Platzhalter mit einer Obergrenze .T
ist die oberste Klasse in der Vererbungshierarchie. Verwenden Sie einenextends
Platzhalter , wenn Sie nur Holen Sie Werte aus einer Struktur.? super T
(die Familie aller Typen, die Supertypen sindT
) - ein Platzhalter mit einer Untergrenze .T
ist die unterste Klasse in der Vererbungshierarchie. Verwenden Sie einensuper
Platzhalter , 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 verwendenT
)Generika und Beispiele
quelle
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 kannUse when you both get and put
.?
- 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önnennull
Sie nicht zu einer mit parametrierten Sammlung hinzufügen?
.quelle
? extends B
bedeutet B und alles , was B erweitert.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 onsumersuper
.Beachten Sie, dass Sie im Allgemeinen nur
? extends T
und? super T
für die Parameter einer Methode verwenden sollten. Methoden sollten nurT
als Typparameter für einen generischen Rückgabetyp verwendet werden.quelle
Kurz gesagt, drei einfache Regeln, an die Sie sich bei PECS erinnern sollten:
<? extends T>
Platzhalter, wenn Sie ein Objekt vom TypT
aus einer Sammlung abrufen müssen .<? super T>
Platzhalter, wenn Sie Objekte vom TypT
in eine Sammlung einfügen müssen.quelle
Nehmen wir diese Hierarchie an:
Lassen Sie uns PE - Producer Extends klarstellen:
Warum können Sie dieser Liste keine Objekte hinzufügen, die "Shark" erweitern? mögen:
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:
... 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:
Was und warum können Sie dieser Liste hinzufügen?
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.):
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 darunterT
zum hinzuzufügenList
. Wir können nicht daraus lesen.Wir verwenden
<? extends T>
, um Objekte vom Typ gleich oder niedrigerT
aus der Liste zu lesen . Wir können kein Element hinzufügen.quelle
(Hinzufügen einer Antwort, da nie genug Beispiele mit Generics-Platzhaltern vorhanden sind)
quelle
Dies ist für mich der klarste und einfachste Weg, an Extend vs. Super zu denken:
extends
ist zum Lesensuper
ist zum SchreibenIch 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".
quelle
src
unddst
. 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.Die PECS "Regel" stellt lediglich sicher, dass Folgendes legal ist:
?
ist, es kann sich legal darauf beziehenT
?
ist, es kann legal von bezeichnet werdenT
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:
Veranschaulichen dies in Bezug auf die Zuordnung Analogie für
consumer
den?
Platzhalter (unbekannter Typ) wird die Referenz - die „linke Seite“ der Zuordnung - und<? super T>
sorgt dafür , dass alles , was?
ist,T
„IS-A“?
- , dieT
ihm zugeordnet werden können, weil?
ist ein Supertyp (oder höchstens der gleiche Typ) wieT
.Für
producer
die 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 wieT
.quelle
Merk dir das:
quelle
Beispiel aus dem wirklichen Leben (mit einigen Vereinfachungen):
<? super FreightCarSize>
<? extends DepotSize>
quelle
Kovarianz : Subtypen akzeptieren
Kontravarianz : Supertypen akzeptieren
Kovariante Typen sind schreibgeschützt, während kontravariante Typen schreibgeschützt sind.
quelle
Schauen wir uns ein Beispiel an
Mit Generics können Sie auf sichere Weise dynamisch mit Typen arbeiten
Problem
Da Javas Sammlung daher ein Referenztyp ist, haben wir folgende Probleme:
Problem Nr. 1
* Swifts Generikum hat kein solches Problem, da Collection
Value type
[About] ist und daher eine neue Collection erstellt wirdProblem Nr. 2
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ügenLö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 besetzenquelle