Une question existentielle, il y a t'il une utilité à écrire :
au lieu de :Code:List<? extends ITruc> getTrucs()
Merci !Code:List<ITruc> getTrucs()
Toine
Version imprimable
Une question existentielle, il y a t'il une utilité à écrire :
au lieu de :Code:List<? extends ITruc> getTrucs()
Merci !Code:List<ITruc> getTrucs()
Toine
Salut,
Si ca existe c'est qu'il y a bien une raison.
Pour l'explication et pour plus de clarté je vais utiliser Number à la place de ITruc. Pour rappel Number est la classe parente des types numériques de base (Integer, Float, Double, ...).
Quelques exemples de listes paramétré
- List<Number> est une liste de Number. Tu peux donc y mettre n'importe quels "Number" y compris les classes filles. Par contre ces dernières seront vu en tant que Number...
- List<Integer> est une liste d'Integer, et tu ne peux y mettre que des Integer (puisqu'il n'existe pas de classes filles).
- List<Double> est une liste de Double, et tu ne peux y mettre que des Double (puisqu'il n'existe pas de classes filles).
Ces trois listes sont totalement incompatible et tu ne peux pas faire ceci :
La raison est simple, car si c'était possible tu pourrais faire ceci par la suite :Code:
1
2 List<Integer> integerList = new ArrayList<Integer>(); List<Number> numberList = integerList; // ERREUR (même avec un cast)
:arrow: Mais du coup cela reviendrait à mettre un Double dans une List<Integer> ce qui serait incorrect !Code:numberList.add(new Double(0.0)); // On ajoute un Double dans une List<Number>, ce qui est tout à fait correct
Pourtant il peut y avoir de nombreux cas où l'on pourrait avoir besoin de faire cela.
Par exemple si on code une méthode qui calculerait la somme des Number via la méthode doubleValue() :
:arrow: On ne peut pas utiliser cette méthode avec une List<Integer> ou une List<Double> et il faut la réécrire :?Code:
1
2
3
4
5
6
7 public double sum(List<Number> numbers) { double sum = 0.0; for (Number n : numbers) { sum += n.doubleValue(); } return sum; }
C'est là que les wilcards entre en jeu (? extends ou ? super).
Avec les wilcards on ne connait pas le type exact du paramétrage, mais on a simplement une information sur son type.
- List<? extends Number> signifie que tu utilises une List paramétrée avec un type héritant de Number (ou Number lui même). Ainsi ce pourrait être une List<Number>, une List<Integer>, une List<Double> ou autres...
Du coup tu peux faire ceci sans problème :
En contrepartie, tu ne peux pas utiliser les méthodes paramétré comme add(T), car tu ignores complètement le type réel de T. Cela permet justement d'éviter d'ajouter un Double dans une liste d'Integer ;)Code:
1
2 List<Integer> integerList = new ArrayList<Integer>(); List<? extends Number> numberList = integerList; // OK
Par contre tu peux très bien utiliser les méthodes qui n'utilises pas de type paramétré ou qui se contente de le renvoyer, comme T get(int) ou Iterator<T> iterator(), et dans ce cas T sera supposé à Number. C'est correct puisque tu sais que T est forcément un type fils de Number (ou lui-même).
Bref en quelques sortes tu n'as accès qu'aux méthodes paramétrés "de lecture", et donc la méthode sum() peut se transformer comme ceci :
:arrow: Elle est désormais utilisable avec toutes les List<T> paramétré avec un type fils de Number ;)Code:
1
2
3
4
5
6
7 public double sum(List<? extends Number> numbers) { double sum = 0.0; for (Number n : numbers) { // parcours via Iterator<? extends Number> sum += n.doubleValue(); } return sum; }
- A l'inverse, List<? super Number> signifie que tu utilises une List paramétrée avec un type parent de Number (ou d'une interface implémenté par Number). Dans ce cas précis ce pourrait être List<Number>, List<Serializable> ou List<Object>.
Ici c'est l'inverse, tu ne peux pas utiliser les méthodes renvoyant T car tu ignores son type réel et que cela peut être un type parent. Par contre tu peux utiliser les méthodes utilisant T en paramétre car tu sais qu'en utilisant un Number tu utilises forcément un type fils de T...
:arrow: Tu as le droits de faire add(new Integer(0)) mais pas Number n = get(0) car tu n'est pas sûr de recevoir un Number...
Ce cas est surement moins utile, et je pense que le meilleur exemple vient de la méthode Collections.addAll() :
:arrow: On ajoute des objets de type T dans une collection acceptant des types T (bref une collection paramétré avec T ou un parent).Code:public static <T> boolean addAll(Collection<? super T> c, T... elements) {
En fait les wildcards sont assez puissant car il permet de rajouter plein de contraintes ;)
J'espère avoir été clair...
a++
Euh... impec' :) Même moi, j'ai enfin compris :P
Par contre, une typo : au dernier point de la liste, tu écrisAu lieu deCitation:
A l'inverse, List<? extends Number> signifie que ...
Cela corrigé, direction la FAQ ? :)Citation:
A l'inverse, List<? super Number> signifie que ...
je comprends mieux en effet, c'est tout à fait logique... 8O
par contre, l'écriture devient très très lourde :(
J'aurai tendance à penser que pour les arguments de méthodes, il vaut mieux utiliser le wildcard pour quelle soit le plus utilisable possible.
Mais peut être plutôt renvoyé le type plus précis en retour ?
Qu'en penses-tu ?
Toine
Je pense qu'il faut lire d'urgence Simplyfing Java Generics by Eliminating Wildcards.
Il arrive qu'elles soient utiles, mais en général, non. Quand je suis obligé de les employer, je me dis que j'ai mal fait ma conception quelque part. (mais je les utilise quand même, en attendant d'avoir de meilleures idées).
Très intéressant en effet !
Mais pour l'instant, comment faire ... :cry:
Perso je ne suis pas trop d'accord avec ce texte : si on supprime les wildcards on perd le coté typesafe des Generics... :?
Je ne penses pas que les wildcards soient une erreur de conception... par contre c'est vrai que cela peut devenir un peu illisible :?
Ben cela dépend de tes besoins :
- Soit tu veux imposer un type paramétré précis tu n'utilises pas de wildcard.
- Si tu veux utiliser un type paramétré "générique" tu utilises les wildcard.
:arrow: Cela dépend de ton besoin il n'y a pas de solution universelle !
a++
La plupart du temps, il suffit de se demander de quoi on a vraiment besoin, dans un contexte précis, et non pas comment être générique. S'il apparait qu'on a besoin de générique, alors c'est bon. Mais la généricité ne doit jamais être un concept en l'air, utilisé comme si c'était un but en soi.
Si on reprend l'exemple avec les listes de Number qui peuvent pas être des Integer, Il faut travailler au niveau de la logique d'ensemble, par rapport à un contexte et un usage précis. Au lieu de te poser la question seulement sur une méthode générique particulière, il faut te poser la question sur la classe générique dans son ensemble.
Et, pour résoudre ce problème, tu aboutiras par exemple à quelque chose comme :
Et tu l'utiliseras, dans un contexte local, très simplement comme suit :Code:
1
2
3
4
5 class GeneriqueNumber<T extends Number> { ArrayList<T> renvoieListe(); }
Plus de wildcard ! N'est pas plus simple ?Code:
1
2
3
4
5
6 GeneriqueNumber<Integer> myNumberIsAnInteger; ArrayList<Intger> listeInteger; myNumberIsAnInteger = new GeneriqueNumber<Integer>(); liseInteger = myNumberIsAnInteger.renvoieListe();
Le corollaire est que toute ta classe doit être conçue pour que chaque instance gère un même type T, au lieu d'avoir une classe générique dont l'une des méthodes gèrerait des Number, une autre des Integer, etc.
(non compilé non testé c'est l'esprit qui compte).