905 Stimmen

Was ist PECS (Producer Extends Consumer 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 ?

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.

29voto

ColinD Punkte 106101

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 von T ist auch ein T .

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 von T kann legal an jede Methode übergeben werden, die einen Supertyp von T . A Comparator<Number> könnte in einem Collection<Integer> zum Beispiel. ? extends T würde nicht funktionieren, weil ein Comparator<Integer> konnte nicht auf einer Collection<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.

2 Stimmen

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?

3 Stimmen

@FatihArslan schauen Sie nicht in die Komparator-Implementierung. Sie ist irrelevant. Die Methode sort(List<T>,Comparator<? super T>) deklariert die Typgrenzen und in diesem sort Methode, der Komparator verbraucht T Instanzen.

19voto

Daniel Punkte 342

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.

19voto

starriet Punkte 990

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:

enter image description here

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.

enter image description here

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! :)

5 Stimmen

Die beste Antwort, die ich je gesehen habe

0 Stimmen

Die beste Antwort, die ich je gesehen habe!

16voto

kaan Punkte 3263

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

1 Stimmen

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.

10voto

Andrejs Punkte 9352

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

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.

Powered by:

X