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) ;