Une très bonne explication avec un exemple @ youtube.com/watch?v=34oiEq9nD0M&feature=youtu.be&t=1630 qui explique une superpartie mais donne une idée d'une autre.
lupchiazoem
Réponses:
844
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 extendsou 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 Thinglorsque 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 Thingla 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 Thingsoit 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 Thingd'ajouter un; c'est ce qui ? super Thinggarantit.
J'essaie toujours de penser de cette façon: un producteur est autorisé à produire quelque chose de plus spécifique, donc s'étend , un consommateur est autorisé à accepter quelque chose de plus général, donc super .
Feuermurmel
10
Une autre façon de se souvenir de la distinction producteur / consommateur est de penser à une signature de méthode. Si vous avez une méthode 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).
Raman
3
@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 caractères génériques ici, ou est-ce juste un moyen d'améliorer la lisibilité similaire à, disons, l'utilisation de références à constcomme paramètres de méthode en C ++ pour signifier que la méthode ne modifie pas les arguments?
Chatterjee
7
@Raman, je pense que vous venez de le confondre. Dans doSthWithList (vous pouvez avoir List <? Super Thing>), puisque vous êtes un consommateur, vous pouvez utiliser super (rappelez-vous, CS). Cependant, c'est List <? étend Thing> getList () puisque vous êtes autorisé à retourner quelque chose de plus spécifique lors de la production (PE).
masterxilo
4
@AZ_ Je partage votre sentiment. Si une méthode obtient () de la liste, la méthode sera considérée comme un consommateur <T> et la liste est considérée comme un fournisseur; mais la règle de PECS est «du point de vue de la liste», donc «étend» est nécessaire. Ce devrait être GEPS: obtenir s'étend; mettre super.
Treefish Zhang
561
Les principes derrière cela en informatique sont appelés
Covariance: ? extends MyClass,
Contravariance: ? super MyClasset
Invariance / non-variance: MyClass
L'image ci-dessous devrait expliquer le concept. Photo gracieuseté: Andrey Tyukin
Salut tout le monde. Je suis Andrey Tyukin, je voulais juste confirmer que 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 "quelques votes"): C'est parce que c'est un croquis pour Scala, il utilise la syntaxe Scala et suppose une variance de site de déclaration, ce qui est assez différent de l'appel bizarre de Java -écart de site ... Je devrais peut-être écrire une réponse plus détaillée qui montre clairement comment ce croquis s'applique à Java ...
Andrey Tyukin
3
C'est l'une des explications les plus simples et les plus claires de la covariance et de la contravariance que j'aie jamais trouvées!
cs4r
@Andrey Tyukin Salut, je veux aussi utiliser cette image. Comment puis-je te contacter?
slouc
Si vous avez des questions sur cette illustration, nous pouvons en discuter dans le salon de
Utilisez un caractère générique étendu lorsque vous obtenez uniquement des valeurs d'une structure.
Utilisez un super caractère générique lorsque vous mettez uniquement des valeurs dans une structure.
Et n'utilisez pas de caractère générique lorsque vous obtenez et mettez.
Exemple en Java:
classSuper{Object testCoVariance(){returnnull;}//Covariance of return types in the subtype.void testContraVariance(Object parameter){}// Contravariance of method arguments in the subtype.}classSubextendsSuper{@OverrideString testCoVariance(){returnnull;}//compiles successfully i.e. return type is don't care(String is subtype of Object) @Overridevoid testContraVariance(String parameter){}//doesn't support even though String is subtype of Object}
Les types de données en lecture seule (sources) peuvent être covariants ;
les types de données en écriture seule (récepteurs) peuvent être contravariants .
Les types de données mutables qui agissent à la fois comme sources et comme récepteurs devraient être invariants .
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 []
covariant : un chat [] est un animal [];
contravariant : un animal [] est un chat [];
invariant : un animal [] n'est pas un chat [] et un chat [] n'est pas un animal [].
Exemples Java:
Object name=newString("prem");//worksList<Number> numbers =newArrayList<Integer>();//gets compile time errorInteger[] 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=newArrayList<>();
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:
In-variance / Non-variance: ?or ? extends Object- Unbounded générique . Il représente la famille de tous types. Utilisez lorsque vous obtenez et mettez.
Co-variance: ? 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 . Test la classe supérieure de la hiérarchie d'héritage. Utilisez un extendscaractère générique lorsque vous obtenez uniquement des valeurs d'une structure.
Contre-variance: ? super T(la famille de tous les types qui sont des supertypes T) - un caractère générique avec une borne inférieure . Test la classe la plus basse de la hiérarchie d'héritage. Utilisez un supercaractè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)
classShape{void draw(){}}classCircleextendsShape{void draw(){}}classSquareextendsShape{void draw(){}}classRectangleextendsShape{void draw(){}}publicclassTest{/*
* Example for an upper bound wildcard (Get values i.e Producer `extends`)
*
* */publicvoid testCoVariance(List<?extendsShape> list){
list.add(newShape());// Error: is not applicable for the arguments (Shape) i.e. inheritance is not supporting
list.add(newCircle());// Error: is not applicable for the arguments (Circle) i.e. inheritance is not supporting
list.add(newSquare());// Error: is not applicable for the arguments (Square) i.e. inheritance is not supporting
list.add(newRectangle());// Error: is not applicable for the arguments (Rectangle) i.e. inheritance is not supportingShape 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`)
* */publicvoid testContraVariance(List<?superShape> list){
list.add(newShape());//compiles i.e. inheritance is supporting
list.add(newCircle());//compiles i.e. inheritance is supporting
list.add(newSquare());//compiles i.e. inheritance is supporting
list.add(newRectangle());//compiles i.e. inheritance is supportingShape shape= list.get(0);// Error: Type mismatch, so list acts only as consumerObject 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.
*/}}
Hé, je voulais juste savoir ce que vous vouliez dire avec la dernière phrase: "Si vous pensez que mon analogie est fausse, veuillez mettre à jour". Voulez-vous dire si c'est éthiquement mauvais (ce qui est subjectif) ou si c'est faux dans le contexte de la programmation (ce qui est objectif: non, ce n'est pas faux)? Je voudrais le remplacer par un exemple plus neutre qui est universellement acceptable indépendamment des normes culturelles et des croyances éthiques; Si cela vous convient.
Neuron
enfin je pouvais l'obtenir. Belle explication.
Oleg Kuts
2
@Premraj,, 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.
LiuWenbin_NO.
1
@LiuWenbin_NO. - Cette partie de la réponse est trompeuse. ?- 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 nullvous ne pouvez pas ajouter à une collection paramétrée avec ?.
mouselabs
29
publicclassTest{publicclass A {}publicclass B extends A {}publicclass C extends B {}publicvoid 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);}publicvoid 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}}
Donc "? Étend B" doit être interprété comme "? B étend". C'est quelque chose que B étend afin d'inclure toutes les super classes de B jusqu'à Object, à l'exception de B lui-même. Merci pour le code!
Saurabh Patil
3
@SaurabhPatil Non, ? extends Bsignifie B et tout ce qui prolonge B.
asgs
24
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 Tdevrait être utilisé, car toute instance d'une sous-classe de Test également un T.
Lorsqu'un type paramétré transmis à une méthode consommera des instances deT (elles lui seront transmises pour faire quelque chose), ? super Tdoit être utilisé car une instance de Tpeut légalement être transmise à toute méthode acceptant un certain type de T. Un Comparator<Number>pourrait être utilisé sur un Collection<Integer>, par exemple. ? extends Tne fonctionnerait pas, car un Comparator<Integer>ne pouvait pas opérer sur un Collection<Number>.
Notez qu'en général, vous ne devez utiliser ? extends Tet ? super Tpour les paramètres que d'une méthode. Les méthodes doivent simplement être utilisées Tcomme paramètre de type sur un type de retour générique.
Ce principe s'applique-t-il uniquement aux collections? Cela a du sens quand on essaie de le corréler avec une liste. Si vous pensez à la signature de tri (Liste <T>, Comparateur <? Super T>) ---> ici le Comparateur utilise super donc cela signifie qu'il est un consommateur dans le contexte PECS. Lorsque vous regardez par exemple l'implémentation: public int compare (Person a, Person b) {return a.age <b.age? -1: a.age == b.age? 0: 1; } J'ai l'impression que la personne ne consomme rien mais produit de l'âge. Cela me rend confus. Y a-t-il une faille dans mon raisonnement ou PECS ne s'applique qu'aux collections?
Fatih Arslan
24
En bref, trois règles faciles à retenir PECS:
Utilisez le <? extends T>caractère générique si vous devez récupérer un objet de type Tdans une collection.
Utilisez le <? super T>caractère générique si vous devez mettre des objets de typeT dans une collection.
Si vous devez satisfaire les deux choses, eh bien, n'utilisez aucun caractère générique. Aussi simple que cela.
classCreature{}// XclassAnimalextendsCreature{}// YclassFishextendsAnimal{}// ZclassSharkextendsFish{}// AclassHammerSkarkextendsShark{}// BclassDeadHammerSharkextendsHammerSkark{}// C
Clarifions PE - Producer Extends:
List<?extendsShark> sharks =newArrayList<>();
Pourquoi vous ne pouvez pas ajouter des objets qui étendent "Shark" dans cette liste? comme:
sharks.add(newHammerShark());//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(newHammerShark());
... 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<?superShark> sharks =newArrayList<>();
Quoi et pourquoi vous pouvez ajouter à cette liste?
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 worksAnimal 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 Tau List. Nous ne pouvons pas en lire. Nous utilisons <? extends T>pour lire des objets de types égaux ou inférieurs Tdans 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);// DestinationList<Integer> intList2 =newArrayList<>();List<Double> doublesList2 =newArrayList<>();List<Number> numList2 =newArrayList<>();// Works
copyElements1(intList,intList2);// from int to int
copyElements1(doubleList,doublesList2);// from double to doublestatic<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)privatestatic<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:
extendsest pour la lecture
superest 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".
J'ai rencontré cette même collision mentale et j'aurais tendance à être d'accord, sauf que PECS ne spécifie pas la dénomination du code et que les limites de type elles - mêmes sont définies dans les déclarations de collection. De plus, en ce qui concerne la dénomination, vous avez souvent des noms pour produire / consommer des collections comme srcet 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.
mouselabs
4
La "règle" PECS garantit simplement que ce qui suit est légal:
Consommation: ce qui ?est, il peut légalement se référer àT
Producteur: quoi qu'il en ?soit, il peut être légalement désigné parT
L'appariement typique dans le sens de List<? extends T> producer, List<? super T> consumers'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, consumerle ?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" ?- Tpeut lui être attribué, car ?est un super type (ou tout au plus le même type) que T.
Car producerle problème est le même, il est juste inversé: producerle ?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):
Imaginez un train de marchandises avec des wagons de marchandises comme analogie avec une liste.
Vous pouvez placer une cargaison dans un wagon de marchandises si la cargaison a la même taille ou une taille plus petite que le wagon de marchandises =<? super FreightCarSize>
Vous pouvez décharger une cargaison d'un wagon de marchandises si vous avez suffisamment de place (plus que la taille de la cargaison) dans votre dépôt =<? extends DepotSize>
publicclass A {}//B is Apublicclass B extends A {}//C is Apublicclass C extends A {}
Les génériques vous permettent de travailler avec les types de manière dynamique et en toute sécurité
//ListAList<A> listA =newArrayList<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);
//ListBList<B> listB =newArrayList<B>();//add
listB.add(new B());//get
B b0 = listB.get(0);
Problème
É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;
La solution - Wildcards génériques
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
<? 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.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'utilisersuper
. Si vous faites les deux avec la même collection, vous ne devez pas utiliser niextends
ousuper
.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 deThing
, et donc chaque élément se comportera comme unThing
lorsque vous effectuez votre opération. (Vous ne pouvez en fait rien ajouter à unCollection<? extends Thing>
, car vous ne pouvez pas savoir au moment de l'exécution quel sous-type spécifique deThing
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 quelqueThing
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 permettraThing
d'ajouter un; c'est ce qui? super Thing
garantit.la source
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 estList 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
la source
PECS (producteur
extends
et consommateursuper
)mnémonique → Principe Get and Put.
Ce principe stipule que:
Exemple en Java:
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
Covariance et contravariance
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:
plus d'exemples
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 deT
) - 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 unextends
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 supertypesT
) - 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 unsuper
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 utilisonsT
)génériques et exemples
la source
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 êtreUse 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 denull
vous ne pouvez pas ajouter à une collection paramétrée avec?
.la source
? 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 onsumersuper
.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éesT
comme paramètre de type sur un type de retour générique.la source
En bref, trois règles faciles à retenir PECS:
<? extends T>
caractère générique si vous devez récupérer un objet de typeT
dans une collection.<? super T>
caractère générique si vous devez mettre des objets de typeT
dans une collection.la source
supposons cette hiérarchie:
Clarifions PE - Producer Extends:
Pourquoi vous ne pouvez pas ajouter des objets qui étendent "Shark" dans cette liste? comme:
É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:
... 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:
Quoi et pourquoi vous pouvez ajouter à cette liste?
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):
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érieursT
auList
. Nous ne pouvons pas en lire.Nous utilisons
<? extends T>
pour lire des objets de types égaux ou inférieursT
dans la liste. Nous ne pouvons pas y ajouter d'élément.la source
(ajout d'une réponse car jamais assez d'exemples avec des caractères génériques génériques)
la source
C'est la façon la plus claire et la plus simple pour moi de penser à l'extension vs super:
extends
est pour la lecturesuper
est pour écrireJe 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".
la source
src
etdst
. 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é parT
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):
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?
estT
"IS-A"?
-T
peut lui être attribué, car?
est un super type (ou tout au plus le même type) queT
.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) queT
.la source
Rappelez-vous ceci:
la source
En utilisant un exemple réel (avec quelques simplifications):
<? super FreightCarSize>
<? extends DepotSize>
la source
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.
la source
Regardons l'exemple
Les génériques vous permettent de travailler avec les types de manière dynamique et en toute sécurité
Problème
É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
* 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ééeProblème n ° 2
La solution - Wildcards génériques
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' ajouterSolution 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 lancerla source