Réponses:
tl; dr: "PECS" est du point de vue de la collection. Si vous ne tirez que des éléments d'une collection générique, c'est un producteur et vous devez utiliser extends
; si vous ne rembourrez que des articles, c'est un consommateur et vous devez l'utiliser super
. Si vous faites les deux avec la même collection, vous ne devez pas utiliser ni extends
ou super
.
Supposons que vous ayez une méthode qui prend comme paramètre une collection de choses, mais que vous souhaitiez qu'elle soit plus flexible que d'accepter simplement a 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 que un Collection<? extends Thing>
pourrait contenir n'importe quel sous-type de Thing
, et donc chaque élément se comportera comme un Thing
lorsque vous effectuez votre opération. (Vous ne pouvez en fait 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 ici est que, contrairement à Collection<? extends Thing>
, Collection<? super Thing>
peut toujours tenir quelque Thing
soit le type paramétré réel. Ici, vous ne vous souciez pas de ce qui est déjà dans la liste tant qu'il permettra Thing
d'ajouter un; c'est ce qui ? super Thing
garantit.
doSomethingWithList(List list)
, vous consommez la liste et donc vous aurez besoin de covariance / étend (ou une liste invariante). D'un autre côté, si votre méthode est List doSomethingProvidingList
, alors vous produisez la liste et vous aurez besoin de contravariance / super (ou d'une liste invariante).
const
comme paramètres de méthode en C ++ pour signifier que la méthode ne modifie pas les arguments?
Les principes derrière cela en informatique sont appelés
? extends MyClass
,? super MyClass
etMyClass
L'image ci-dessous devrait expliquer le concept. Photo gracieuseté: Andrey Tyukin
PECS (producteur extends
et consommateur super
)
mnémonique → Principe Get and Put.
Ce principe stipule que:
Exemple en Java:
class Super {
Object testCoVariance(){ return null;} //Covariance of return types in the subtype.
void testContraVariance(Object parameter){} // Contravariance of method arguments in the subtype.
}
class Sub extends Super {
@Override
String testCoVariance(){ return null;} //compiles successfully i.e. return type is don't care(String is subtype of Object)
@Override
void testContraVariance(String parameter){} //doesn't support even though String is subtype of Object
}
Principe de substitution de Liskov: si S est un sous-type de T, alors les objets de type T peuvent être remplacés par des objets de type S.
Dans le système de type d'un langage de programmation, une règle de frappe
Pour illustrer ce phénomène général, considérons le type de tableau. Pour le type Animal, nous pouvons faire le type Animal []
Exemples Java:
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
caractère générique délimité (c'est-à-dire en direction de quelque part) : il existe 3 variantes différentes de caractères génériques:
?
or ? extends Object
- Unbounded générique . Il représente la famille de tous types. Utilisez lorsque vous obtenez et mettez.? extends T
(la famille de tous les types qui sont des sous-types de T
) - un caractère générique avec une limite supérieure . T
est la classe supérieure de la hiérarchie d'héritage. Utilisez un extends
caractère générique lorsque vous obtenez uniquement des valeurs d'une structure.? super T
(la famille de tous les types qui sont des supertypes T
) - un caractère générique avec une borne inférieure . T
est la classe la plus basse de la hiérarchie d'héritage. Utilisez un super
caractère générique lorsque vous placez uniquement des valeurs dans une structure.Remarque: le caractère générique ?
signifie zéro ou une fois , représente un type inconnu. Le caractère générique peut être utilisé comme type de paramètre, jamais utilisé comme argument de type pour un appel de méthode générique, une création d'instance de classe générique (c'est-à-dire lorsqu'il est utilisé comme caractère générique cette référence non utilisée ailleurs dans un 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 Test {
/*
* 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<? super Shape>
* You can't get an Shape(but can get Object) and don't know what kind of Shape it is.
*/
}
}
In-variance/Non-variance: ? or ? extends Object - Unbounded Wildcard. It stands for the family of all types. Use when you both get and put.
je ne peux pas ajouter d'élément à List <?> Ou List <? étend Object>, donc je ne comprends pas pourquoi cela peut être Use when you both get and put
.
?
- le "caractère générique illimité" - correspond à l'opposé exact de l'invariance. Veuillez vous référer à la documentation suivante: docs.oracle.com/javase/tutorial/java/generics/… qui stipule: Dans le cas où le code doit accéder à la variable en tant que variable "in" et "out", faites ne pas utiliser de caractère générique. (Ils utilisent "in" et "out" comme synonymes de "get" et "put"). À l'exception de null
vous ne pouvez pas ajouter à une collection paramétrée avec ?
.
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
}
}
? extends B
signifie B et tout ce qui prolonge B.
Comme je l'explique dans ma réponse à une autre question, PECS est un dispositif mnémonique créé par Josh Bloch pour aider à se souvenir de P roducer extends
, C onsumer super
.
Cela signifie que lorsqu'un type paramétré transmis à une méthode produira des instances de
T
(elles en seront extraites d'une manière ou d'une autre),? extends T
devrait être utilisé, car toute instance d'une sous-classe deT
est également unT
.Lorsqu'un type paramétré transmis à une méthode consommera des instances de
T
(elles lui seront transmises pour faire quelque chose),? super T
doit être utilisé car une instance deT
peut légalement être transmise à toute méthode acceptant un certain type deT
. UnComparator<Number>
pourrait être utilisé sur unCollection<Integer>
, par exemple.? extends T
ne fonctionnerait pas, car unComparator<Integer>
ne pouvait pas opérer sur unCollection<Number>
.
Notez qu'en général, vous ne devez utiliser ? extends T
et ? super T
pour les paramètres que d'une méthode. Les méthodes doivent simplement être utilisées T
comme paramètre de type sur un type de retour générique.
En bref, trois règles faciles à retenir PECS:
<? extends T>
caractère générique si vous devez récupérer un objet de type T
dans une collection.<? super T>
caractère générique si vous devez mettre des objets de typeT
dans une collection.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 des objets qui étendent "Shark" dans cette liste? comme:
sharks.add(new HammerShark());//will result in compilation error
Étant donné que 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 en effet voir au moment de la compilation que vous ajoutez un B:
sharks.add(new HammerShark());
... mais il n'a aucun moyen de savoir si lors de l'exécution, votre B sera un sous-type ou un supertype du type liste. Au moment de l'exécution, le type de liste peut être n'importe lequel des types A, B, C. Vous ne pouvez donc pas finir par ajouter HammerSkark (super type) dans une liste de DeadHammerShark par exemple.
* Vous direz: "OK, mais pourquoi ne puis-je pas y ajouter HammerSkark car c'est le plus petit type?". Réponse: C'est le plus petit que vous connaissez. Mais HammerSkark peut également être étendu 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 ce qui se trouve en dessous du requin (A, B, C) sera toujours des sous-types de tout ce qui se trouve au-dessus du 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 plus élevé dans la hiérarchie que le type déclaré de la liste (X, Y, Z). Ce n'est pas permis.
Mais pourquoi vous ne pouvez pas lire cette liste? (Je veux dire que vous pouvez en extraire 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
Au moment de l'exécution, le type de liste peut être de n'importe quel type au-dessus de A: X, Y, Z, ... Le compilateur peut compiler votre instruction d'affectation (qui semble correcte) mais, au moment de l' exécution, le type de s (Animal) peut être inférieur dans hiérarchie que le type déclaré de la liste (qui pourrait être Créature ou supérieur). Ce n'est pas permis.
Pour résumer
Nous utilisons <? super T>
pour ajouter des objets de types égaux ou inférieurs T
au List
. Nous ne pouvons pas en lire.
Nous utilisons <? extends T>
pour lire des objets de types égaux ou inférieurs T
dans la liste. Nous ne pouvons pas y ajouter d'élément.
(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);
}
}
C'est la façon la plus claire et la plus simple pour moi de penser à l'extension vs super:
extends
est pour la lecture
super
est pour écrire
Je trouve que "PECS" est une façon non évidente de penser aux choses concernant qui est le "producteur" et qui est le "consommateur". « PECS » est défini du point de vue de la collecte des données elle - même - la collection « consume » si les objets sont en cours d' écriture à (il consomme des objets de code d' appel), et « produit » si les objets sont lus à partir (il produit des objets vers un code appelant). C'est contraire à la façon dont tout le reste est nommé. Les API Java standard sont nommées du point de vue du code appelant, et non de la collection elle-même. Par exemple, une vue centrée sur la collection de java.util.List doit avoir une méthode nommée "receive ()" au lieu de "add ()" - après tout,l'élément, mais la liste elle-mêmereçoit l'élément.
Je pense qu'il est plus intuitif, naturel et cohérent de penser les choses du point de vue du code qui interagit avec la collection - le code "lit-il" ou "écrit-il" dans la collection? Ensuite, tout code écrit dans la collection serait le "producteur", et tout code lu dans la collection serait le "consommateur".
src
et dst
. Donc, vous traitez à la fois du code et des conteneurs en même temps et j'ai fini par y penser de la même manière: «consommer du code» consomme à partir d'un conteneur producteur et «produire du code» produit pour un conteneur consommateur.
La "règle" PECS garantit simplement que ce qui suit est légal:
?
est, il peut légalement se référer à T
?
soit, il peut être légalement désigné par T
L'appariement typique dans le sens de List<? extends T> producer, List<? super T> consumer
s'assure simplement que le compilateur peut appliquer les règles de relation d'héritage "IS-A" standard. Si nous pouvions le faire légalement, cela pourrait être plus simple à dire <T extends ?>, <? extends T>
(ou mieux encore à Scala, comme vous pouvez le voir ci-dessus, c'est [-T], [+T]
. Malheureusement, le mieux que nous puissions faire est <? super T>, <? extends T>
.
Quand j'ai rencontré cela pour la première fois et que je suis tombé en panne dans ma tête, la mécanique avait du sens, mais le code lui-même a continué à sembler déroutant pour moi - je pensais toujours "il semble que les limites ne devraient pas avoir besoin d'être inversées comme ça" - même si je était clair sur ce qui précède - qu'il s'agit simplement de garantir le respect des règles de référence standard.
Ce qui m'a aidé, c'est de le regarder en utilisant une affectation ordinaire comme analogie.
Considérez le code de jouet suivant (non prêt pour la production):
// copies the elements of 'producer' into 'consumer'
static <T> void copy(List<? extends T> producer, List<? super T> consumer) {
for(T t : producer)
consumer.add(t);
}
Pour illustrer cela en termes d'analogie d'affectation, consumer
le ?
caractère générique (type inconnu) est la référence - le "côté gauche" de l'affectation - et <? super T>
garantit que tout ce qui ?
est T
"IS-A" ?
- T
peut lui être attribué, car ?
est un super type (ou tout au plus le même type) que T
.
Car producer
le problème est le même, il est juste inversé: producer
le ?
caractère générique de (type inconnu) est le référent - le "côté droit" de l'affectation - et <? extends T>
garantit que tout ce qui ?
est, ?
"IS-A" T
- qu'il peut être attribué à unT
, car il ?
s'agit d'un sous-type (ou au moins du même type) que T
.
En utilisant un exemple réel (avec quelques simplifications):
<? super FreightCarSize>
<? extends DepotSize>
Covariance : accepter les sous-types
Contravariance : accepter les supertypes
Les types covariants sont en lecture seule, tandis que les types contravariants sont en écriture seule.
Regardons l'exemple
public class A { }
//B is A
public class B extends A { }
//C is A
public class C extends A { }
Les génériques vous permettent de travailler avec les types de manière dynamique et en toute sécurité
//ListA
List<A> listA = new ArrayList<A>();
//add
listA.add(new A());
listA.add(new B());
listA.add(new C());
//get
A a0 = listA.get(0);
A a1 = listA.get(1);
A a2 = listA.get(2);
//ListB
List<B> listB = new ArrayList<B>();
//add
listB.add(new B());
//get
B b0 = listB.get(0);
Étant donné que la collection Java est un type de référence en conséquence, nous avons les prochains problèmes:
Problème n ° 1
//not compiled
//danger of **adding** non-B objects using listA reference
listA = listB;
* Le générique de Swift n'a pas un tel problème car Collection est Value type
[About] donc une nouvelle collection est créée
Problème n ° 2
//not compiled
//danger of **getting** non-B objects using listB reference
listB = listA;
Le caractère générique est une fonctionnalité de type référence et ne peut pas être instancié directement
Solution n ° 1
<? super A>
aka borne inférieure aka contravariance aka consommateurs garantit qu'il est exploité par A et toutes les superclasses, c'est pourquoi il est sûr d' ajouter
List<? super A> listSuperA;
listSuperA = listA;
listSuperA = new ArrayList<Object>();
//add
listSuperA.add(new A());
listSuperA.add(new B());
//get
Object o0 = listSuperA.get(0);
Solution n ° 2
<? extends A>
aka limite supérieure aka covariance aka producteurs garantit qu'il est exploité par A et toutes les sous-classes, c'est pourquoi il est sûr d' obtenir et de lancer
List<? extends A> listExtendsA;
listExtendsA = listA;
listExtendsA = listB;
//get
A a0 = listExtendsA.get(0);
super
partie mais donne une idée d'une autre.