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
?
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.
Les principes derrière cela en informatique s'appelle
? extends MyClass
,? super MyClass
etMyClass
L'image ci-dessous devrait expliquer le concept.
Courtoisie d'image: Andrey Tyukin
PECS (abréviation de "Producteur extends
et Consumer super
") peut être expliqué par: Principe Get and Put
Il est dit,
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);
}
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
}
}
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 deT
est aussi unT
.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 deT
peut légalement être passé à n'importe quelle méthode qui accepte un sur-type deT
. UnComparator<Number>
peut être utilisé sur unCollection<Integer>
, par exemple.? extends T
ne fonctionnerait pas, car unComparator<Integer>
ne pourrait pas fonctionner sur unCollection<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.
En bref, trois règles faciles à retenir de PECS:
<? extends T>
si vous devez extraire un objet de type Type T
d'une collection.<? super T>
si vous devez placer des objets de type T
dans Une collection.(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);
}
}
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.
Rappelez-vous ceci:
Consommateur manger souper (super); Producteur agrandit l'usine de ses parents
En utilisant des exemples concrets (avec quelques simplifications):
<? super FreightCarSize>
<? extends DepotSize>
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.