Qu'est-ce que PECS (Producer Extends Consumer Super)?
11 réponses
tl;dr: "PECS" est du point de vue de la collection. Si vous êtes seulement tirer des articles d'une collection générique, il est un producteur et vous devez utiliser extends
; si vous êtes seulement articles de farce dans, Il est un consommateur et vous devez utiliser super
. Si vous faites les deux avec la même collection, vous ne devriez pas utiliser extends
ou super
.
supposons que vous ayez une méthode qui prend comme paramètre un ensemble de choses, mais vous voulez qu'elle soit plus flexible que simplement accepter un Collection<Thing>
.
Case 1: vous voulez parcourir la collection et faire des choses avec chaque article.
Ensuite, la liste est un producteur , donc vous devez utiliser un Collection<? extends Thing>
.
le raisonnement est qu'un Collection<? extends Thing>
pourrait contenir n'importe quelle sous-type de Thing
, et donc chaque élément se comporte comme un Thing
lorsque vous effectuez votre opération. (En fait, vous ne pouvez rien ajouter à un Collection<? extends Thing>
, parce que vous ne pouvez pas savoir à l'exécution quel spécifique sous-type de Thing
la collection tient.)
Case 2: vous voulez ajouter des choses à la collection.
Alors la liste est un consommateur , donc vous devriez l'utilisation d'un Collection<? super Thing>
.
le raisonnement ici est que contrairement à Collection<? extends Thing>
, Collection<? super Thing>
peut toujours tenir un Thing
peu importe ce que le type paramétré réel est. Ici, vous ne vous souciez pas de ce qui est déjà dans la liste tant qu'il permettra à un Thing
; c'est ce que ? super Thing
de garanties.
Les principes derrière cette en Sciences de l'Informatique est nommé d'après
- Covariance -? extend MyClass,
- Contravariance -? super MyClass et
- Invariance / non-Variance-MyClass
L'image ci-dessous devrait expliquer le concept.
Photo courtoisie: Andrey Tyukin
PECS (abréviation de producteur extends
et consommateur super
") peut être expliqué par: Get and Put Principle
Get Et Put Principe (à Partir de Java Génériques et Collections)
indique le,
- utiliser un rallonge Joker quand vous seulement get valeurs hors d'une structure
- utiliser un Super Joker quand vous seulement mettre valeurs dans une structure
- et n'utilisez pas de Joker lorsque vous obtenez et mettez .
comprenons-le par l'exemple:
1. Pour Extend Wildcard (get values I. e producteur extends
)
Voici une méthode, qui prend une collection de nombres, convertit chaque un double
, et résume les
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()
la méthode utilise extends
, tous les appels suivants sont légaux.
Les deux premiers appels ne seraient pas légaux si les prolongations n'étaient pas utilisées.
EXCEPTION : vous ne pouvez rien mettre dans un type déclaré avec un extends
Joker-sauf pour la valeur null
, qui appartient à chaque type de référence:
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 Joker (put values I. e consommateur super
)
Voici une méthode, qui prend une collection de nombres et un int n
, et met le premier n
entiers, à 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, count()
la méthode utilise 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 ne peut rien obtenir d'un type déclaré avec un super
Joker-sauf pour 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. Quand les deux obtenir et mettre, n'utilisez pas de Joker
chaque fois que vous à la fois mettre valeurs dans et obtenir valeurs de la même structure, vous ne doit pas utiliser de jointure .
public static double sumCount(Collection<Number> nums, int n) {
count(nums, n);
return sum(nums);
}
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
}
}
PECS (Producteur de extends
et de la Consommation super
)
mnémonique → obtenir et mettre Principe.
ce principe stipule que:
- utilisez un joker extends lorsque vous obtenez seulement des valeurs hors d'une structure.
- utilisez un Super Joker lorsque vous mettez seulement des valeurs dans une structure.
- et n'utilisez pas de Joker quand vous obtenez et mettez.
en Java, les paramètres et les paramètres génériques de type ne prennent pas en charge l'héritage comme suit.
class Super {
void testCoVariance(Object parameter){} // method Consumes the Object
Object testContraVariance(){ return null;} //method Produces the Object
}
class Sub extends Super {
@Override
void testCoVariance(String parameter){} //doesn't support eventhough String is subtype of Object
@Override
String testContraVariance(){ return null;} //compiles successfully i.e. return type is don't care
}
principe de substitution de Liskov: les tableaux sont covariants(dangereux) mais les génériques ne sont pas i.e. invariants(sûrs). i.e. principe de Substitution ne fonctionne pas avec les types paramétrés, ce qui signifie qu'il est illégal d'écrire.
Covariant signifie simplement si X
est sous-type de Y
puis X[]
sera également sous-type de Y[]
.
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
bornded (i.e. heading toward somewhere) wildcard : il existe 3 saveurs différentes de wildcards:
- Dans de variance/Non-variance:
?
ou? extends Object
- Illimité "de 1519410920" Joker. Il représente la famille de tous les types. Utilisez quand vous obtenez et mis. - Co-variance:
? extends T
(la famille de tous les types qui sont des sous - types deT
) - un joker avec un limite supérieure .T
est la supérieure - la classe la plus dans la hiérarchie de l'héritage. Utilisez un jokerextends
lorsque vous n'avez que les valeurs Get d'une structure. - Contra-variance:
? super T
(la famille de tous les types qui sont des supertypes deT
) - un joker avec un limite inférieure .T
est le inférieur - la classe la plus dans la hiérarchie de l'héritage. Utilisez un jokersuper
lorsque vous mettez seulement des valeurs dans une structure.
Note: le caractère générique ?
signifie zéro ou une fois , représente type inconnu. Le Joker peut être utilisé comme le type d'un paramètre, jamais utilisé comme argument de type pour une invocation de méthode générique, une création d'instance de classe générique.(c'est-à-dire lorsqu'on utilise le caractère de remplacement qui n'est pas utilisé ailleurs dans le programme comme nous utilisons 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 TestContraVariance {
/*
* 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<? extends Shape>
* You can't get an Shape(but can get Object) and don't know what kind of Shape it is.
*/
}
}
comme je l'explique dans ma réponse à une autre question, PECS est un dispositif mnémotechnique créé par Josh Bloch pour aider à se souvenir P roducer extends
, c onsumer super
.
Cela signifie que lorsqu'un type paramétré passé à une méthode produire cas de
T
(ils seront récupérées à partir d'une certaine façon),? extends T
doit être utilisé, puisque toute instance d'une sous-classe deT
est aussi unT
.quand un type paramétré étant passé à une méthode sera consommer instances de
T
(ils seront passés à elle pour faire quelque chose),? super T
doit être utilisé parce qu'une instance deT
peut légalement être passé à toute méthode qui accepte un certain supertype deT
. UnComparator<Number>
pourrait être utilisé sur unCollection<Integer>
, exemple.? extends T
ne fonctionnerait pas, parce qu'unComparator<Integer>
ne pourrait pas fonctionner sur unCollection<Number>
.
notez qu'en général vous ne devez utiliser ? extends T
et ? super T
que pour les paramètres d'une méthode. Les méthodes doivent juste utiliser T
comme paramètre de type sur un type de retour Générique.
en bref facile à se rappeler PECS
- utilisez le Joker
<? extends T>
si vous avez besoin de récupérer l'objet de tapezT
d'une collection. - utilisez le Joker
<? super T>
si vous avez besoin de mettre des objets de typeT
dans collection. - si vous avez besoin de satisfaire les deux choses, n'utilisez pas de Joker. Comme simple qu'il soit.
(ajouter une réponse parce que jamais assez d'exemples avec des caractères génériques wildcards)
// 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);
}
}
rappelez-vous ceci:
"151900920 du Consommateur" manger cène (super); Producteur s'étend de ses parents usine
assumons 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 des objets qui étendent "Shark" dans cette liste? comme:
sharks.add(new HammerShark());//will result in compilation error
puisque vous avez une liste qui peut être de type A, B ou C à l'exécution , vous ne pouvez pas y ajouter d'objet de type A, B ou C parce que vous pouvez finir avec une combinaison qui est pas autorisés en java.
dans la pratique, le compilateur peut effectivement voir à l'heure du compilateur que vous ajoutez un B:
sharks.add(new HammerShark());
...mais il n'a aucun moyen de dire si à l'exécution, Votre B sera un sous-type ou supertype du type de liste. À l'exécution, le type de liste peut être n'importe lequel des types A, B, C. Ainsi, vous ne pouvez pas finir par ajouter HammerSkark (super type) dans une liste de DeadHammerShark par exemple.
*Vous allez dire: "OK, mais pourquoi ne puis-je pas ajouter HammerSkark en elle puisque c'est le plus petit type?". Réponse: C'est le plus petit vous savoir. Acheter HammerSkark peut être augmenté 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<>();
Ce qui et pourquoi vous peut ajouter à cette liste?
sharks.add(new Shark());
sharks.add(new DeadHammerShark());
sharks.add(new HammerSkark());
vous pouvez ajouter les types d'objets ci-dessus parce que tout ce qui est en dessous de requin(A,B,C) sera toujours des sous-types de tout ce qui est au-dessus de requin (X,Y,Z). Facile à comprendre.
Vous ne peut pas ajouter des types ci-dessus de Requin, parce que au moment de l'exécution le type de l'objet ajouté peut être plus élevé dans la hiérarchie que le type déclaré de la liste(X,Y,Z). Ce n'est pas autorisé.
mais pourquoi ne pouvez-vous pas lire à partir de cette liste? (Je veux dire que vous peut en retirer un élément, mais vous ne pouvez pas l'affecter à autre chose que L'objet o):
Object o;
o = sharks.get(2);// only assignment that works
Animal s;
s = sharks.get(2);//doen't work
à l'exécution, le type de liste peut être n'importe quel type au-dessus de 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 que le type déclaré de la liste (qui pourrait être créature, ou supérieur). Ce n'est pas autorisé.
pour résumer
nous utilisons <? super T>
pour ajouter des objets de types égaux ou inférieurs à T dans la liste. nous ne pouvons pas lire de
il.
nous utilisons <? extends T>
pour lire les objets des types égaux ou inférieurs à T de la liste. nous ne pouvons pas y ajouter d'élément.
les caractères génériques peuvent être utilisés de trois façons:"
- Upper bound Wildcard ( ? extends Type ). - Lower bound Wildcard ( ? super Type ) . - Unbounded Wildcard ( ? ) .
pour les besoins de cette discussion, il est utile de penser aux variables comme fournissant l'une des deux fonctions:
- In Variable An "in" variable serves up data to the code. Imagine a copy method with two arguments: copy(src, dest) The src argument provides the data to be copied, so it is the "in" parameter. - Out Variable An "out" variable holds data for use elsewhere. In the copy example, copy(src, dest) the dest argument accepts data, so it is the "out" parameter. An "in" variable is defined with an upper bounded wildcard, using the extends keyword. An "out" variable is defined with a lower bounded wildcard, using the super keyword. In the case where the "in" variable can be accessed using methods defined in the Object class, use an unbounded wildcard. In the case where the code needs to access the variable as both an "in" and an "out" variable, do not use a wildcard. class NaturalNumber { private int i; public NaturalNumber(int i) { this.i = i; } } class EvenNumber extends NaturalNumber { public EvenNumber(int i) { super(i); } } Consider the following code: List<EvenNumber> le = new ArrayList<>(); List<? extends NaturalNumber> ln = le; ln.add(new NaturalNumber(35)); // compile-time error You can add null. You can invoke clear. You can get the iterator and invoke remove. You can capture the wildcard and write elements that you've read from the list.