Ich stieß auf PECS (kurz für Produzent extends
und Verbraucher super
), während ich mich über Generika informierte.
Kann mir jemand erklären, wie man PECS einsetzt, um die Verwirrung zwischen extends
y super
?
Ich stieß auf PECS (kurz für Produzent extends
und Verbraucher super
), während ich mich über Generika informierte.
Kann mir jemand erklären, wie man PECS einsetzt, um die Verwirrung zwischen extends
y super
?
Wie ich in meine Antwort zu einer anderen Frage: PECS ist eine Gedächtnisstütze, die von Josh Bloch entwickelt wurde, um die Erinnerung an P roducer **e**xtends
, C onsumer **s**uper
.
Das bedeutet, dass ein parametrisierter Typ, der an eine Methode übergeben wird produzieren Instanzen von
T
(sie werden auf irgendeine Weise daraus abgerufen),? extends T
verwendet werden sollte, da jede Instanz einer Unterklasse vonT
ist auch einT
.Wenn ein parametrisierter Typ an eine Methode übergeben wird, wird verbrauchen Instanzen von
T
(sie werden an ihn weitergeleitet, damit er etwas tut),? super T
verwendet werden sollte, da eine Instanz vonT
kann legal an jede Methode übergeben werden, die einen Supertyp vonT
. AComparator<Number>
könnte in einemCollection<Integer>
zum Beispiel.? extends T
würde nicht funktionieren, weil einComparator<Integer>
konnte nicht auf einerCollection<Number>
.
Beachten Sie, dass Sie im Allgemeinen nur ? extends T
y ? super T
für die Parameter einer bestimmten Methode. Methoden sollten einfach T
als Typ-Parameter für einen generischen Rückgabetyp.
Gilt dieser Grundsatz nur für Sammlungen? Es macht Sinn, wenn man versucht, es mit einer Liste in Verbindung zu bringen. Betrachtet man die Signatur von sort(List<T>,Comparator<? super T>) ---> hier verwendet der Comparator super, was bedeutet, dass er im PECS-Kontext ein Konsument ist. Wenn man sich die Implementierung anschaut, zum Beispiel: 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 verbraucht, sondern nur Alter produziert. Das macht mich verwirrt. Gibt es einen Fehler in meiner Argumentation oder gilt PECS nur für Collections?
Gehen wir von dieser Hierarchie aus:
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
Erklären wir PE - Producer Extends:
List<? extends Shark> sharks = new ArrayList<>();
Warum kann man Objekte, die "Shark" erweitern, nicht in diese Liste aufnehmen? z. B:
sharks.add(new HammerShark());//will result in compilation error
Da Sie eine Liste haben, die vom Typ A, B oder C sein kann während der Laufzeit können Sie kein Objekt des Typs A, B oder C hinzufügen, da Sie eine Kombination erhalten können, die in Java nicht zulässig ist.
In der Praxis kann der Compiler in der Tat zur Compilierzeit erkennen, dass Sie ein B hinzufügen:
sharks.add(new HammerShark());
...aber es gibt keine Möglichkeit festzustellen, ob Ihr B zur Laufzeit ein Subtyp oder Supertyp des Listentyps ist. Zur Laufzeit kann der Listentyp jeder der Typen A, B, C sein. Sie können also nicht am Ende HammerSkark (Supertyp) in eine Liste von DeadHammerShark einfügen, zum Beispiel.
*Sie werden sagen: "OK, aber warum kann ich nicht HammerSkark hinzufügen, da es der kleinste Typ ist?". Antwort: Er ist der kleinste usted kennen. Aber HammerSkark kann auch von jemand anderem erweitert werden und man landet im selben Szenario.
Klären wir CS - Consumer Super:
In der gleichen Hierarchie können wir dies versuchen:
List<? super Shark> sharks = new ArrayList<>();
Was und warum Sie peut zu dieser Liste hinzufügen?
sharks.add(new Shark());
sharks.add(new DeadHammerShark());
sharks.add(new HammerSkark());
Sie können die oben genannten Arten von Objekten hinzufügen, da alles unterhalb von shark(A,B,C) immer Subtypen von allem oberhalb von shark (X,Y,Z) sind. Leicht zu verstehen.
Sie kann nicht Typen über Shark hinzufügen, denn während der Laufzeit der Typ des hinzugefügten Objekts kann in der Hierarchie höher sein als der deklarierte Typ der Liste (X,Y,Z). Dies ist nicht zulässig.
Aber warum können Sie nicht aus dieser Liste lesen? (Ich meine, man kann ein Element aus der Liste herauslesen, aber man kann es keinem anderen Objekt zuordnen):
Object o;
o = sharks.get(2);// only assignment that works
Animal s;
s = sharks.get(2);//doen't work
Zur Laufzeit kann der Typ der Liste jeder Typ oberhalb von A sein: X, Y, Z, ... Der Compiler kann Ihre Zuweisungsanweisung (die korrekt zu sein scheint) kompilieren, aber, während der Laufzeit der Typ von s (Tier) kann in der Hierarchie niedriger sein als der deklarierte Typ der Liste (der Kreatur oder höher sein kann). Dies ist nicht zulässig.
Zusammenfassend
Wir verwenden <? super T>
um Objekte vom Typ gleich oder kleiner hinzuzufügen T
zum List
. Wir können nicht lesen aus es.
Wir verwenden <? extends T>
um Objekte vom Typ gleich oder kleiner zu lesen T
von der Liste. Wir können ihr keine Elemente hinzufügen.
Lassen Sie uns versuchen, dieses Konzept zu visualisieren.
<? super SomeType>
ist ein "undefinierter (noch)" Typ, aber dieser undefinierte Typ sollte ein super Klasse der Klasse 'SomeType'.
Das Gleiche gilt für <? extends SomeType>
. Es ist eine Art, die sollte erweitern die Klasse "SomeType" (sie sollte eine Unterklasse der Klasse "SomeType" sein).
Wenn wir das Konzept der "Klassenvererbung" in einem Venn-Diagramm betrachten, könnte ein Beispiel so aussehen:
Klasse der Säugetiere erweitert Tierklasse (Die Tierklasse ist eine super Klasse der Säugetiere).
Klasse Katze/Hund erweitert Säugetierklasse (Die Säugetierklasse ist eine super Klasse der Klasse Katze/Hund).
Stellen wir uns die "Kreise" im obigen Diagramm als eine "Box" vor, die ein physikalisches Volumen hat.
Man kann eine größere Schachtel nicht in eine kleinere Schachtel stecken.
Sie können NUR eine kleinere Schachtel in eine größere Schachtel stecken.
Wenn Sie sagen <? super SomeType>
Sie wollen eine "Box" beschreiben, die die gleiche Größe hat oder größer als das Feld "SomeType".
Wenn Sie sagen <? extends SomeType>
dann wollen Sie eine "Box" beschreiben, die die gleiche Größe hat oder kleiner als das Feld "SomeType".
Was ist PECS eigentlich?
Ein Beispiel für einen "Producer" ist eine Liste, aus der wir nur lesen.
Ein Beispiel für einen "Verbraucher" ist eine Liste, in die wir nur schreiben.
Denken Sie einfach an Folgendes:
Wir "lesen" von einem "Produzenten" und nehmen das Material in unsere eigene Box.
Und wir 'schreiben' unsere eigene Box in einen 'Verbraucher'.
Wir müssen also etwas von einem "Produzenten" lesen (nehmen) und diese in unsere 'Box' legen. Dies bedeutet, dass alle vom Erzeuger übernommenen Kisten NICHT größer sein als unsere "Box". Deshalb " P roducer E xtends".
"Erweitert" bedeutet ein kleineres Feld (kleinerer Kreis im obigen Venn-Diagramm). Die Kästchen eines Produzenten sollten kleiner sein als unser eigenes Kästchen, denn wir nehmen die Kästchen des Produzenten und legen sie in unser eigenes Kästchen. Wir können nichts hineinlegen, was größer ist als unsere Box!
Außerdem müssen wir schreiben(stellen) unsere eigene 'Box' in ein "Verbraucher". Dies bedeutet, dass die Boxen des Verbrauchers NICHT kleiner sein als unsere eigene Box. Deshalb " C onsumer S uper".
"Super" bedeutet eine größere Box (größerer Kreis im Venn-Diagramm oben). Wenn wir unsere eigenen Boxen in einen Verbraucher stecken wollen, sollten die Boxen des Verbrauchers größer sein als unsere Box!
Jetzt können wir dieses Beispiel leicht verstehen:
public class Collections {
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i = 0; i < src.size(); i++)
dest.set(i, src.get(i));
}
}
In dem obigen Beispiel wollen wir etwas lesen (nehmen) von src
und schreiben sie in dest
. Also die src
ist ein "Produzent" und seine "Boxen" sollten kleiner (spezifischer) sein als ein Typ T
.
Umgekehrt ist die dest
ist ein "Verbraucher" und seine "Boxen" sollten größer (allgemeiner) sein als ein Typ T
.
Wenn die "Boxen" des src
waren größer als die des dest
Wir konnten die großen Kartons nicht in die kleineren Kartons stecken, die dest
hat.
Falls jemand dies liest, hoffe ich, dass es zum besseren Verständnis beiträgt " P roducer E xtends, C onsumer S uper".
Viel Spaß beim Codieren! :)
Dies ist für mich der klarste und einfachste Weg, um an "extends" und "super" zu denken:
extends
ist für Lesen
super
ist für Schreiben
Ich finde, dass "PECS" eine nicht offensichtliche Art ist, darüber nachzudenken, wer der "Produzent" und wer der "Konsument" ist. "PECS" wird aus der Perspektive des "Produzenten" definiert. die Datenerhebung selbst - die Sammlung "verbraucht", wenn Objekte geschrieben werden zu es (es verbraucht Objekte aus dem aufrufenden Code), und es "produziert", wenn Objekte gelesen werden von es (es produziert Objekte für einen aufrufenden Code). Dies steht im Gegensatz zu dem, wie alles andere benannt ist. Standard-Java-APIs werden aus der Perspektive des aufrufenden Codes benannt, nicht aus der der Sammlung selbst. Zum Beispiel ist eine sammlungsorientierte Sichtweise von java.util.Liste eine Methode mit dem Namen "receive()" anstelle von "add()" haben sollte - schließlich ist der aufrufende Code fügt hinzu. das Element, sondern die Liste selbst erhält das Element.
Ich denke, es ist intuitiver, natürlicher und konsistenter, die Dinge aus der Perspektive des Codes zu betrachten, der mit der Sammlung interagiert - liest der Code von" oder schreibt er in" die Sammlung? Daraus folgt, dass jeder Code Schreiben an die Sammlung wäre der "Produzent", und jeder Code Ablesen von die Sammlung wäre der "Verbraucher".
Ich bin auf dieselbe gedankliche Kollision gestoßen und würde dem tendenziell zustimmen, außer dass PECS die Benennung des Codes und die Typgrenzen selbst nicht spezifiziert son in den Sammlungsdeklarationen festgelegt. Was die Benennung betrifft, so haben Sie oft Namen für erzeugende/verbrauchende Collections wie src
y dst
. Man hat es also gleichzeitig mit Code und Containern zu tun, und ich habe mir das so überlegt - "konsumierender Code" konsumiert von einem produzierenden Container, und "produzierender Code" produziert für einen konsumierenden Container.
(Hinzufügen einer Antwort, da es nie genug Beispiele mit Generics-Platzhaltern gibt)
// 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);
}
}
CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.
6 Stimmen
Eine sehr gute Erklärung mit einem Beispiel @ youtube.com/watch?v=34oiEq9nD0M&feature=youtu.be&t=1630, das erklärt
super
Teil, sondern gibt eine Vorstellung von einem anderen.