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 sollten super
. Wenn Sie beide mit derselben Sammlung ausführen, sollten Sie weder extends
oder 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 Thing
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ügen Collection<? extends Thing>
, da Sie zur Laufzeit nicht wissen können, welcher bestimmte Subtyp Thing
der 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, Thing
egal was der tatsächliche parametrisierte Typ ist. Hier ist es Ihnen egal, was bereits in der Liste enthalten ist, solange ein Thing
Hinzufügen hinzugefügt werden kann. das ? super Thing
garantiert.
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).
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
PECS (Produzent extends
und Konsument super
)
Mnemonik → Get and Put-Prinzip.
Dieses Prinzip besagt:
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
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:
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
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 sind T
) - ein Platzhalter mit einer Obergrenze . T
ist die oberste Klasse in der Vererbungshierarchie. Verwenden Sie einen extends
Platzhalter , wenn Sie nur Holen Sie Werte aus einer Struktur.? super T
(die Familie aller Typen, die Supertypen sind T
) - ein Platzhalter mit einer Untergrenze . T
ist die unterste Klasse in der Vererbungshierarchie. Verwenden Sie einen super
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 verwenden T
)
class Shape { void draw() {}}
class Circle extends Shape {void draw() {}}
class Square extends Shape {void draw() {}}
class Rectangle extends Shape {void draw() {}}
public class Test {
/*
* Example for an upper bound wildcard (Get values i.e Producer `extends`)
*
* */
public void testCoVariance(List<? extends Shape> list) {
list.add(new Shape()); // Error: is not applicable for the arguments (Shape) i.e. inheritance is not supporting
list.add(new Circle()); // Error: is not applicable for the arguments (Circle) i.e. inheritance is not supporting
list.add(new Square()); // Error: is not applicable for the arguments (Square) i.e. inheritance is not supporting
list.add(new Rectangle()); // Error: is not applicable for the arguments (Rectangle) i.e. inheritance is not supporting
Shape shape= list.get(0);//compiles so list act as produces only
/*You can't add a Shape,Circle,Square,Rectangle to a List<? extends Shape>
* You can get an object and know that it will be an Shape
*/
}
/*
* Example for a lower bound wildcard (Put values i.e Consumer`super`)
* */
public void testContraVariance(List<? super Shape> list) {
list.add(new Shape());//compiles i.e. inheritance is supporting
list.add(new Circle());//compiles i.e. inheritance is supporting
list.add(new Square());//compiles i.e. inheritance is supporting
list.add(new Rectangle());//compiles i.e. inheritance is supporting
Shape shape= list.get(0); // Error: Type mismatch, so list acts only as consumer
Object object= list.get(0); // gets an object, but we don't know what kind of Object it is.
/*You can add a Shape,Circle,Square,Rectangle to a List<? super Shape>
* You can't get an Shape(but can get Object) and don't know what kind of Shape it is.
*/
}
}
In-variance/Non-variance: ? or ? extends Object - Unbounded Wildcard. It stands for the family of all types. Use when you both get and put.
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
.
?
- 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 null
Sie nicht zu einer mit parametrierten Sammlung hinzufügen ?
.
public class Test {
public class A {}
public class B extends A {}
public class C extends B {}
public void testCoVariance(List<? extends B> myBlist) {
B b = new B();
C c = new C();
myBlist.add(b); // does not compile
myBlist.add(c); // does not compile
A a = myBlist.get(0);
}
public void testContraVariance(List<? super B> myBlist) {
B b = new B();
C c = new C();
myBlist.add(b);
myBlist.add(c);
A a = myBlist.get(0); // does not compile
}
}
? extends B
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 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 T
verwendet werden sollte, da jede Instanz einer Unterklasse vonT
auch a istT
.Wenn ein parametrisierter Typ, der an eine Methode übergeben wird , Instanzen von verbraucht
T
(sie werden an sie übergeben, um etwas zu tun),? super T
sollte verwendet werden, da eine Instanz vonT
legal an jede Methode übergeben werden kann, die einen Supertyp von akzeptiertT
. AComparator<Number>
könnteCollection<Integer>
zum Beispiel für a verwendet werden.? extends T
würde nicht funktionieren, weil aComparator<Integer>
nicht an einem arbeiten konnteCollection<Number>
.
Beachten Sie, dass Sie im Allgemeinen nur ? extends T
und ? super T
für die Parameter einer Methode verwenden sollten. Methoden sollten nur T
als Typparameter für einen generischen Rückgabetyp verwendet werden.
Kurz gesagt, drei einfache Regeln, an die Sie sich bei PECS erinnern sollten:
<? extends T>
Platzhalter, wenn Sie ein Objekt vom Typ T
aus einer Sammlung abrufen müssen .<? super T>
Platzhalter, wenn Sie Objekte vom Typ T
in eine Sammlung einfügen müssen.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 T
zum hinzuzufügen List
. Wir können nicht daraus lesen.
Wir verwenden <? extends T>
, um Objekte vom Typ gleich oder niedriger T
aus 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);
// 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);
}
}
Dies ist für mich der klarste und einfachste Weg, an Extend vs. Super zu denken:
extends
ist zum Lesen
super
ist 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".
src
und 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.
Die PECS "Regel" stellt lediglich sicher, dass Folgendes legal ist:
?
ist, es kann sich legal darauf beziehen T
?
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 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“ ?
- , die T
ihm zugeordnet werden können, weil ?
ist ein Supertyp (oder höchstens der gleiche Typ) wie T
.
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 wie T
.
Beispiel aus dem wirklichen Leben (mit einigen Vereinfachungen):
<? super FreightCarSize>
<? extends DepotSize>
Kovarianz : Subtypen akzeptieren
Kontravarianz : Supertypen akzeptieren
Kovariante Typen sind schreibgeschützt, während kontravariante Typen schreibgeschützt sind.
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);
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;
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);
super
Teil erklärt, aber eine Vorstellung von einem anderen gibt.