web-dev-qa-db-fra.com

Qu'est-ce que PECS (Producer Extends Consumer Super)?

Je suis tombé sur PECS (abréviation de Producer extends et Consumer super) en lisant sur les génériques. 

Quelqu'un peut-il m'expliquer comment utiliser PECS pour résoudre la confusion entre extends et super?

616
peakit

tl; dr: "PECS" est du point de vue de la collection. Si vous tirez seulement des éléments d'une collection générique, il s'agit d'un producteur et vous devez utiliser extends; si vous seulement que bourrez des éléments, c'est un consommateur et vous devez utiliser super. Si vous faites les deux avec la même collection, vous ne devez utiliser ni extends ni super.


Supposons que vous ayez une méthode qui prenne pour paramètre une collection de choses, mais que vous souhaitiez qu'elle soit plus flexible que simplement accepter un Collection<Thing>.

Cas 1: Vous voulez parcourir la collection et faire des choses avec chaque article.
Ensuite, la liste est un producteur, vous devez donc utiliser un Collection<? extends Thing>.

Le raisonnement est qu'un Collection<? extends Thing> peut contenir n'importe quel sous-type de Thing, et ainsi chaque élément se comportera comme un Thing lorsque vous effectuerez votre opération. (En réalité, vous ne pouvez rien ajouter à un Collection<? extends Thing>, car vous ne pouvez pas savoir au moment de l'exécution quel sous-type spécifique de Thing la collection contient.)

Cas 2: Vous souhaitez ajouter des éléments à la collection.
Ensuite, la liste est un consommateur, vous devez donc utiliser un Collection<? super Thing>.

Le raisonnement est le suivant: contrairement à Collection<? extends Thing>, Collection<? super Thing> peut toujours contenir une Thing, quel que soit le type paramétré. Ici, vous ne vous souciez pas de ce qui est déjà dans la liste dans la mesure où cela permettra d'ajouter une Thing; C'est ce que ? super Thing garantit.

713
Michael Myers

Les principes derrière cela en informatique s'appelle

  • Covariance: ? extends MyClass,
  • Contravariance: ? super MyClass et
  • Invariance/non-variance: MyClass

L'image ci-dessous devrait expliquer le concept.

Courtoisie d'image: Andrey Tyukin

Covariance vs Contravariance

487
anoopelias

PECS (abréviation de "Producteur extends et Consumer super") peut être expliqué par: Principe Get and Put

Principe d'obtention et de vente (à partir des génériques et des collections de Java)

Il est dit,

  1. utilisez un étend le caractère générique lorsque vous ne faites que obtenir valeurs d'une structure
  2. utilisez un super wildcard lorsque vous ne mettez que mettez valeurs dans une structure
  3. et n'utilisez pas de caractère générique lorsque vous les deux obtenez et mettez}.

Comprenons-le par exemple:

1. Pour les caractères génériques étendus (obtenez les valeurs i.e Producer extends)}

Voici une méthode qui prend une collection de nombres, les convertit chacun en une double et les résume

public static double sum(Collection<? extends Number> nums) {
   double s = 0.0;
   for (Number num : nums) 
      s += num.doubleValue();
   return s;
}

Appelons la méthode:

List<Integer>ints = Arrays.asList(1,2,3);
assert sum(ints) == 6.0;
List<Double>doubles = Arrays.asList(2.78,3.14);
assert sum(doubles) == 5.92;
List<Number>nums = Arrays.<Number>asList(1,2,2.78,3.14);
assert sum(nums) == 8.92;

Depuis, la méthode sum() utilise la variable extends, tous les appels suivants sont légaux . Les deux premiers appels ne seraient pas légaux si la commande extend n'était pas utilisée.

EXCEPTION: vous ne pouvez rien mettre dans un type déclaré avec un caractère générique extends, à l'exception de la valeur null, qui appartient à chaque référence. type:

List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
List<? extends Number> nums = ints;
nums.add(null);  // ok
assert nums.toString().equals("[1, 2, null]");

2. Pour Super Wildcard (mettez les valeurs i.e Consumer super)}

Voici une méthode, qui prend une collection de nombres et un int n, et place les premiers nombres entiers n, à partir de zéro, dans la collection:

public static void count(Collection<? super Integer> ints, int n) {
    for (int i = 0; i < n; i++) ints.add(i);
}

Appelons la méthode:

List<Integer>ints = new ArrayList<Integer>();
count(ints, 5);
assert ints.toString().equals("[0, 1, 2, 3, 4]");
List<Number>nums = new ArrayList<Number>();
count(nums, 5); nums.add(5.0);
assert nums.toString().equals("[0, 1, 2, 3, 4, 5.0]");
List<Object>objs = new ArrayList<Object>();
count(objs, 5); objs.add("five");
assert objs.toString().equals("[0, 1, 2, 3, 4, five]");

Depuis, la méthode count() utilise super, tous les appels suivants sont légaux: Les deux derniers appels ne seraient pas légaux si super n'avait pas été utilisé.

EXCEPTION: vous ne pouvez rien obtenir d'un type déclaré avec un caractère générique super, à l'exception d'une valeur de type Object, qui est un supertype de chaque type de référence:

List<Object> objs = Arrays.<Object>asList(1,"two");
List<? super Integer> ints = objs;
String str = "";
for (Object obj : ints) str += obj.toString();
assert str.equals("1two");

3. Lorsque Get et Put sont tous deux utilisés, n'utilisez pas de caractère générique

Chaque fois que vous mettez tous les deux valeurs dans et obtenez valeurs de la même structure, vous ne devez pas utiliser de caractère générique.

public static double sumCount(Collection<Number> nums, int n) {
   count(nums, n);
   return sum(nums);
}
142
Prateek
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
    }
}
27
Gab

Comme je l'explique dans ma réponse à une autre question, PECS est un dispositif mnémonique créé par Josh Bloch pour aider à mémoriser P roducer extends, C onsumer super.

Cela signifie que quand un type paramétré transmis à une méthode sera produit instances de T (elles seront extraites de cette manière), ? extends T devrait être utilisé, car toute instance d'une sous-classe de T est aussi un T.

Lorsqu'un type paramétré transmis à une méthode sera consommé instances de T (elles lui seront transmises pour faire quelque chose), ? super T doit être utilisé car une instance de T peut légalement être passé à n'importe quelle méthode qui accepte un sur-type de T. Un Comparator<Number> peut être utilisé sur un Collection<Integer>, par exemple. ? extends T ne fonctionnerait pas, car un Comparator<Integer> ne pourrait pas fonctionner sur un Collection<Number>.

Notez qu'en général, vous ne devriez utiliser que ? extends T et ? super T pour les paramètres d'une méthode. Les méthodes doivent simplement utiliser T comme paramètre de type sur un type de retour générique.

21
ColinD

En bref, trois règles faciles à retenir de PECS: 

  1. Utilisez le caractère générique <? extends T> si vous devez extraire un objet de type Type T d'une collection.
  2. Utilisez le caractère générique <? super T> si vous devez placer des objets de type T dans Une collection.
  3. Si vous avez besoin de satisfaire les deux choses, n’utilisez pas de joker. Aussi simple que cela.
19
Pradeep Kr Kaushal

(ajout d'une réponse car jamais assez d'exemples avec des caractères génériques génériques)

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

Supposons cette hiérarchie:

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

Clarifions PE - Producer Extends:

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

Pourquoi vous ne pouvez pas ajouter d'objets qui s'étendent "Shark" dans cette liste? comme:

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

Comme vous avez une liste qui peut être de type A, B ou C au moment de l'exécution , vous ne pouvez pas y ajouter d'objet de type A, B ou C car vous pouvez vous retrouver avec une combinaison non autorisée en Java. . 
En pratique, le compilateur peut effectivement voir lors de la compilation que vous ajoutez un B:

sharks.add(new HammerShark());

... mais il n'a aucun moyen de savoir si, au moment de l'exécution, votre B sera un sous-type ou un supertype du type de liste. Au moment de l'exécution, le type de liste peut être l'un des types A, B, C. Vous ne pouvez donc pas ajouter HammerSkark (super type) dans une liste de DeadHammerShark, par exemple.

* Vous allez dire: "OK, mais pourquoi ne puis-je pas ajouter HammerSkark à celui-ci puisqu'il s'agit du type le plus petit?". Réponse: C'est le plus petit vous savez. Buy HammerSkark peut aussi être prolongé par quelqu'un d'autre et vous vous retrouvez dans le même scénario.

Clarifions CS - Consumer Super:

Dans la même hiérarchie, nous pouvons essayer ceci:

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

Quoi et pourquoi vous pouvez / ajouter à cette liste?

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

Vous pouvez ajouter les types d'objets ci-dessus car tout élément situé au-dessous d'un requin (A, B, C) sera toujours un sous-type de tout élément situé au-dessus d'un requin (X, Y, Z). Facile à comprendre. 

Vous ne pouvez pas ajouter de types au-dessus de Shark, car au moment de l'exécution le type d'objet ajouté peut être supérieur dans la hiérarchie au type déclaré de la liste (X, Y, Z). Ceci n'est pas autorisé.

Mais pourquoi ne pouvez-vous pas lire à partir de cette liste? (Je veux dire, vous pouvez en extraire un élément, mais vous ne pouvez pas l’affecter à autre chose que Object o):

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

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

Au moment de l'exécution, le type de liste peut être n'importe quel type supérieur à A: X, Y, Z, .... Le compilateur peut compiler votre instruction d'affectation (ce qui semble correct) mais, à l'exécution le type de s (Animal) peut être inférieur dans la hiérarchie au type déclaré de la liste (qui pourrait être Créature, ou supérieur). Ceci n'est pas autorisé.

Pour résumer

Nous utilisons <? super T> pour ajouter des objets de type égal ou inférieur à T dans la liste. Nous ne pouvons pas lire à partir de It.
Nous utilisons <? extends T> pour lire des objets de types égal ou inférieur à T dans la liste. Nous ne pouvons pas ajouter d'élément à cela.

5
Daniel

Rappelez-vous ceci: 

Consommateur manger souper (super); Producteur agrandit l'usine de ses parents

2
Jason

En utilisant des exemples concrets (avec quelques simplifications):

  1. Imaginez un train de marchandises avec des wagons de marchandises comme une analogie avec une liste.
  2. Vous pouvez mettre une cargaison dans un wagon si la cargaison a une taille identique ou inférieure à celle du wagon = <? super FreightCarSize>
  3. Vous pouvez décharger une cargaison d'un wagon de fret si vous avez assez de place (plus que la taille de la cargaison) dans votre dépôt = <? extends DepotSize>
0
contrapost

Covariance : accepte les sous-types
Contravariance : accepte les supertypes

Les types covariants sont en lecture seule, tandis que les types contravariants sont en écriture seulement.

0
Farrukh Chishti