List<T> n'étend jamais
List<U>, quelle que soit la relation qui puisse exister entre
T et
U. Cela dit, la classe
List<T> étend bien
List (le type
raw
).
Cela peut sembler curieux que
List<Integer> ne puisse pas étendre
List<Number>, alors que
Integer étend lui-même
Number. Si cela était le cas, nous aurions en fait de vrais problèmes, tel que celui exposé dans l'exemple suivant.
Exemple 37. Problème de sureté de type générique
// création d'une liste d'Integer List<Integer> integerList = Arrays.asList(1, 2, 3) ; // ce cast n'est pas autorisé et ne compile pas, mais supposons qu'il le soit List<Number> numberList = (List<Number>)listOfInteger ; // il deviendrait possible d'ajouter à integerList des éléments qui ne // seraient pas des Integer ! numberList.add(1.0F) ; // ajout d'un Float, illégal
? permet de résoudre ce problème, tout en conservant la sécurité de nos types génériques. Le principe est de pouvoir écrire une liste
List<? extends Number>, qui elle, est bien étendue par
List<Integer>. Comment peut-on alors protéger l'ajout d'autres éléments que des entiers dans notre liste ? Simplement en interdisant l'utilisation des méthodes prenant
? extends Number en paramètre. Utiliser la méthode
add() définie sur
List<? extends Number> est illégal, puisque le type pris en paramètre n'est pas entièrement connu.
Ce genre de code ne compile donc pas.
Exemple 38. Utilisation du type
? extends T
List<Integer> listOfInteger = Arrays.asList(1, 2, 3) ; // ce cast est correct : List<? extends Number> est bien étendu // par List<Integer> List<? extends Number> listOfNumberExtensions = listOfInteger ; // en revanche cet appel ne compile pas, car la méthode add de // List<? extends Number> prend le type ? extends Number en paramètre, // qui n'est pas connu listOfNumberExtensions.add((Integer)2) ; // cet appel est correct int i = listOfNumberExtensions.get(0) ;
Number comme type de retour pour
get(int). Le véritable type est
?, mais on sait, par construction, que ce type étend
Number.
Dans ce cas, le type de retour du getter est garanti, en revanche celui du setter ne l'est pas.
Notons que la déclaration
? extends T est appelée
bounded wildcard
dans les documentations en anglais.
? étende un type donné, on peut aussi lui imposer d'être le super-type d'un type donné, par la déclaration
? super T. Cette écriture désigne tous les types dont le type
T est un type dérivé.
Reprenons notre exemple précédent.
Exemple 39. Utilisation du type
? super T
List<Integer> listOfInteger = Arrays.asList(1, 2, 3) ; // cette déclaration n'a pas besoin de cast, car List<? super Integer> étend // List<Integer List<? super Integer> listOfNumberExtensions = listOfInteger ; // cet ajout est valide, on peut passer un Integer au add() listOfNumberExtensions.add(2) ; // ce cast compile bien, mais n'est pas sûr, car le type de retour peut être Object int i = (Integer)listOfNumberExtensions.get(0) ;