J'ai quelques questions sur les caractères génériques génériques en Java:
Quelle est la différence entre List<? extends T>
et List<? super T>
?
Qu'est-ce qu'un caractère générique délimité et qu'est-ce qu'un caractère générique illimité?
Dans votre première question, <? extends T>
et <? super T>
sont des exemples de caractères génériques bornés. Un caractère générique illimité ressemble à <?>
, et signifie essentiellement <? extends Object>
. Cela signifie que le générique peut être de n'importe quel type. Un caractère générique délimité (<? extends T>
ou <? super T>
) place une restriction sur le type en disant qu'il doit soit étendre un type spécifique (<? extends T>
est appelé limite supérieure), ou doit être un ancêtre d'un type spécifique (<? super T>
est connu comme une borne inférieure).
Les tutoriels Java ont de très bonnes explications sur les génériques dans les articles caractères génériques et plus de plaisir avec les caractères génériques .
Si vous avez une hiérarchie de classes A, B est une sous-classe de A, et C et D sont tous deux sous-classe de B comme ci-dessous
class A {}
class B extends A {}
class C extends B {}
class D extends B {}
Ensuite
List<? extends A> la;
la = new ArrayList<B>();
la = new ArrayList<C>();
la = new ArrayList<D>();
List<? super B> lb;
lb = new ArrayList<A>(); //fine
lb = new ArrayList<C>(); //will not compile
public void someMethod(List<? extends B> lb) {
B b = lb.get(0); // is fine
lb.add(new C()); //will not compile as we do not know the type of the list, only that it is bounded above by B
}
public void otherMethod(List<? super B> lb) {
B b = lb.get(0); // will not compile as we do not know whether the list is of type B, it may be a List<A> and only contain instances of A
lb.add(new B()); // is fine, as we know that it will be a super type of A
}
Un caractère générique délimité est comme ? extends B
où B est un type. Autrement dit, le type est inconnu, mais un "lié" peut être placé dessus. Dans ce cas, il est délimité par une classe, qui est une sous-classe de B.
Josh Bloch a également une bonne explication du moment d'utiliser super
et extends
dans ce google io video talk où il mentionne le Producteur extends
Consommateur super
mnémonique.
À partir des diapositives de présentation:
Supposons que vous souhaitiez ajouter des méthodes en bloc à
Stack<E>
void pushAll(Collection<? extends E> src);
- src est un producteur E
void popAll(Collection<? super E> dst);
- dst est un consommateur E
Il peut arriver que vous souhaitiez restreindre les types de types autorisés à être transmis à un paramètre de type. Par exemple, une méthode qui fonctionne sur des nombres peut uniquement vouloir accepter des instances de Number ou de ses sous-classes. C'est à cela que servent les paramètres de type borné.
Collection<? extends MyObject>
signifie qu'il peut accepter tous les objets qui ont une relation IS- A avec MyObject (c'est-à-dire n'importe quel objet qui est un type de myObject ou nous pouvons dire n'importe quel objet de n'importe quelle sous-classe de MyObject) ou un objet de la classe MyObject .
Par exemple:
class MyObject {}
class YourObject extends MyObject{}
class OurObject extends MyObject{}
Ensuite,
Collection<? extends MyObject> myObject;
n'acceptera que MyObject ou les enfants de MyObject (c'est-à-dire tout objet de type OurObject ou YourObject ou MyObject, mais pas tout objet de superclasse de MyObject).
En général,
Si une structure contient des éléments avec un type de la forme
? extends E
, nous pouvons extraire des éléments de la structure, mais nous ne pouvons pas mettre d'éléments dans la structure
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
List<? extends Number> nums = ints;
nums.add(3.14); // compile-time error
assert ints.toString().equals("[1, 2, 3.14]");
Pour mettre des éléments dans la structure, nous avons besoin d'un autre type de caractère générique appelé Wildcards with super
,
List<Object> objs = Arrays.<Object>asList(2, 3.14, "four");
List<Integer> ints = Arrays.asList(5, 6);
Collections.copy(objs, ints);
assert objs.toString().equals("[5, 6, four]");
public static <T> void copy(List<? super T> dst, List<? extends T> src) {
for (int i = 0; i < src.size(); i++) {
dst.set(i, src.get(i));
}
}
public class A { }
public class B extends A { }
public class C extends A { }
List<A> listA = new ArrayList<A>();
List<B> listB = new ArrayList<B>();
Le problème
listB = listA; //not compiled
listA = listB; //not compiled
listB = listA;
Dans listA, vous pouvez insérer des objets qui sont soit des instances de A, soit des sous-classes de A (B et C). Quand tu fais listB = listA
vous pourriez risquer que listA
contienne des objets non-B. Lorsque vous essayez ensuite d'extraire des objets de listB
, vous risquez de retirer des objets non-B (par exemple un A ou un C ). Cela rompt le contrat de la déclaration de variable listB
.
listA = listB;
Si vous pouviez effectuer cette affectation, il serait possible d'insérer les instances A et C dans la liste pointée par listB
. Vous pouvez le faire via la référence listA
, qui est déclarée comme List. Ainsi, vous pourriez insérer des objets non-B dans une liste déclarée contenir des instances B (ou sous-classe B).
Comme vous le voyez, le problème se situe dans les affectations . Par exemple, vous ne pouvez pas créer une méthode qui traite les éléments de List qui sont apparentés de cette manière.
Wildcards génériques à la rescousse
Tu devrais utiliser List <? extends A>
aka borne supérieure aka covariance aka producteurs si vous allez lire .get () dans la liste
Lorsque vous savez que les instances de la collection sont des instances de A ou des sous-classes de A, il est sûr de lire les instances de la collection et de les convertir aux instances A.
Vous ne pouvez pas insérer d'éléments dans la liste, car vous ne savez pas si la liste est typée dans la classe A, B ou C.
Tu devrais utiliser List <? super A>
aka borne inférieure aka contravariance aka consommateurs si vous allez insérer .add () dans la liste
Lorsque vous savez que la liste est typée soit A, soit une superclasse de A, il est sûr d'insérer des instances de A ou des sous-classes de A (par exemple B ou C) dans la liste.
Vous ne pouvez cependant pas lire à partir de la liste, sauf s'il convertit les objets lus en objet. Les éléments déjà présents dans la liste peuvent être de tout type qui est soit un A soit une superclasse de A, mais il n'est pas possible de savoir exactement de quelle classe il s'agit.
Veuillez lire plus ici
Des caractères génériques génériques sont créés pour rendre les méthodes qui fonctionnent sur Collection plus réutilisables.
Par exemple, si une méthode a un paramètre List<A>
, nous ne pouvons donner que List<A>
à cette méthode. C'est un gaspillage pour la fonction de cette méthode dans certaines circonstances:
List<A>
, alors nous devrions être autorisés à donner List<A-sub>
à cette méthode. (Parce que A-sub IS a A)List<A>
, alors nous devrions être autorisés à donner List<A-super>
à cette méthode. (Parce que A IS a A-super)