913 votes

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

J'ai découvert le PECS (abréviation de Producteur extends et des consommateurs super ) tout en lisant sur les génériques.

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

6 votes

Une très bonne explication avec un exemple @ youtube.com/watch?v=34oiEq9nD0M&feature=youtu.be&t=1630 qui explique super mais, donne une idée d'une autre partie.

1054voto

Michael Myers Points 82361

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


Supposons que vous ayez une méthode qui prenne comme paramètre une collection d'objets, mais que vous souhaitiez qu'elle soit plus flexible qu'une simple acceptation d'un fichier Collection<Thing> .

Cas 1 : Vous voulez parcourir la collection et faire des choses avec chaque élément.
Alors 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 effectuez votre opération. (En fait, vous ne pouvez rien ajouter (sauf null) à un fichier Collection<? extends Thing> parce que vous ne pouvez pas savoir au moment de l'exécution quelle spécifique sous-type de Thing la collection tient).

Cas 2 : Vous voulez ajouter des choses à la collection.
Alors la liste est un consommateur vous devez donc utiliser un Collection<? super Thing> .

Le raisonnement ici est que, contrairement Collection<? extends Thing> , Collection<? super Thing> peut toujours tenir un Thing quel que soit le type réel paramétré. Ici, vous ne vous souciez pas de ce qui se trouve déjà dans la liste, du moment que cela permet d'utiliser un paramètre Thing à ajouter ; c'est ce que ? super Thing garanties.

209 votes

J'essaie toujours d'y penser de cette façon : A producteur est autorisé à produire quelque chose de plus spécifique, d'où étend , a consommateur est autorisé à accepter quelque chose de plus général, d'où super .

12 votes

Une autre façon de se souvenir de la distinction producteur/consommateur est de penser à la signature d'une méthode. Si vous avez une méthode doSomethingWithList(List list) vous êtes en consommant la liste et aura donc besoin d'une covariance/extensions (ou d'une liste invariante). D'un autre côté, si votre méthode est List doSomethingProvidingList alors vous êtes produisant la Liste et aura besoin de contravariance / super (ou d'une Liste invariante).

3 votes

@MichaelMyers : Pourquoi ne pouvons-nous pas simplement utiliser un type paramétré pour ces deux cas ? Y a-t-il un avantage spécifique à utiliser des jokers ici, ou est-ce juste un moyen d'améliorer la lisibilité, similaire à, disons, l'utilisation de références à const comme paramètres de méthode en C++ pour signifier que la méthode ne modifie pas les arguments ?

685voto

anoopelias Points 1555

Les principes qui sous-tendent cette démarche en informatique sont appelés

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

L'image ci-dessous devrait expliquer le concept. Photo gracieusement offerte : Andrey Tyukin

Covariance vs Contravariance

203 votes

Bonjour à tous. Je suis Andrey Tyukin, je voulais juste confirmer qu'anoopelias & DaoWen m'ont contacté et ont obtenu ma permission d'utiliser le croquis, il est sous licence (CC)-BY-SA. Thx @ Anoop pour lui avoir donné une seconde vie^^ @Brian Agnew : (sur "peu de votes") : C'est parce que c'est un sketch pour Scala, il utilise la syntaxe Scala et suppose une variance de site de déclaration, ce qui est très différent de l'étrange variance de site d'appel de Java... Je devrais peut-être écrire une réponse plus détaillée qui montre clairement comment ce sketch s'applique à Java...

4 votes

C'est l'une des explications les plus simples et les plus claires de la covariance et de la contravariance que j'ai jamais trouvées !

0 votes

@Andrey Tyukin Bonjour, je souhaite également utiliser cette image. Comment puis-je vous contacter ?

148voto

Prateek Points 2253

PECS (short for Producer extends and Consumer super) peut être expliqué par : Get and Put Principle

Principe Get And Put (tiré de Java Generics and Collections)

Il déclare,

  1. utiliser un caractère générique étendu lorsque vous n'obtenez que des valeurs d'une structure
  2. utiliser un super joker lorsque vous ne mettez que des valeurs dans une structure
  3. et n'utilisez pas de joker lorsque vous utilisez à la fois get et put.

Comprenons-le par l'exemple :

1. For Extends Wildcard(get values i.e Producer extends)

Voici une méthode, qui takes a collection of numbers, converts each to a double, and sums them up

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, sum() method uses extends Si vous utilisez extends, tous les appels suivants sont légaux. Les deux premiers appels ne seraient pas légaux si extends n'était pas utilisé.

EXCEPTION : Vous cannot put anything en un type declared with an extends wildcard—except pour la valeur null qui belongs to every reference 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. For Super Wildcard(put values i.e Consumer super)

Voici une méthode, that takes a collection of numbers and an integer n, and puts the first n integers, starting from zero, into the 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, count() method uses super tous les appels suivants sont légaux : Les deux derniers appels ne seraient pas légaux si super n'était pas utilisé.

EXCEPTION : vous cannot get anything à partir d'un type declared with a super wildcard—except pour une valeur de type Object qui est un supertype of every reference type :

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. When both Get and Put, dont Use wildcard

Quand vous deux put values into and get values out of the same structure vous ne devez pas ne pas utiliser de caractère générique.

public static double sumCount(Collection<Number> nums, int n) {
   count(nums, n);
   return sum(nums);
}

33voto

Gab Points 1979
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
    }
}

0 votes

Ainsi, " ? extends B" doit être interprété comme " ? B extends". C'est quelque chose que B étend, ce qui inclut toutes les super classes de B jusqu'à Object, à l'exception de B lui-même. Merci pour le code !

4 votes

@SaurabhPatil Non, ? extends B signifie B et tout ce qui prolonge B.

29voto

ColinD Points 48573

Comme je l'explique dans ma réponse Pour répondre à une autre question, PECS est une méthode mnémotechnique créée par Josh Bloch pour aider à se souvenir P roducteur **e**xtends , C onsommateur **s**uper .

Cela signifie que lorsqu'un type paramétré passé à une méthode produire instances de T (ils seront récupérés d'une manière ou d'une autre), ? extends T devrait être utilisé, puisque toute instance d'une sous-classe de T est également un T .

Lorsqu'un type paramétré passé à une méthode sera consommer instances de T (ils lui seront passés 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 supertype de T . A Comparator<Number> pourrait être utilisé sur un Collection<Integer> par exemple. ? extends T ne fonctionnerait pas, car une Comparator<Integer> ne pouvait 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 devraient simplement utiliser T comme paramètre de type sur un type de retour générique.

2 votes

Ce principe ne vaut-il que pour les collections ? Il prend tout son sens lorsqu'on essaie de le mettre en corrélation avec une liste. Si vous pensez à la signature de sort(List<T>,Comparator< ? super T>) ---> ici le Comparator utilise super donc cela signifie que c'est un consommateur dans le contexte PECS. Lorsque vous regardez l'implémentation par exemple comme : public int compare(Person a, Person b) { return a.age < b.age ? -1 : a.age == b.age ? 0 : 1 ; } J'ai l'impression que Person ne consomme rien mais produit l'âge. Cela me rend confus. Y a-t-il une faille dans mon raisonnement ou PECS ne vaut que pour les Collections ?

3 votes

@FatihArslan ne regardez pas l'implémentation du comparateur. C'est sans intérêt. La méthode sort(List<T>,Comparator<? super T>) déclare les limites du type et dans ce sort le comparateur consomme T instances.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X