Salut,
Intérressant tout ca 
Tout d'abord juste un détail concernant le terme "générique" : tu l'utilises à la place de Generics un peu à tord : la generalisation est un concept de POO lié à l'héritage, et présent depuis la première version de Java (et dans tous les langages objets en général). Tu devrais plutôt utilisé le terme Generics (voir types paramétrés). En effet, les Generics de Java 5.0 permettent simplement d'utiliser la généricité de manière sécurisé afin d'éviter les ClassCastException (le compilateur vérifiera la cohérence du code en quelque sorte).

Envoyé par
professeur shadoko
J'ai des soucis avec la généricité: le compilateur appliquant la règle "dans le doute abstiens toi" je me retrouve de temps en temps dans des impasses.
C'est justement un des objectifs des generics : si le compilateur ne génère aucune erreur ni warning, alors l'utilisation de la généricité est correcte et ne devrait pas provoquer d'exception à l'exécution. Du coup le compilateur semble un peu "pessimiste" en effet (les Generics ont introduit un grand nombre de nouveaux warnings/erreurs).
Concernant ton premier exemple, si tu ne peux pas affecter la référence d'un Iterator<Truc> dans un Iterator<Trucable>, c'est que tu peux te retrouver dans des cas de mauvaise manipulation (pas avec l'interface Iterator qui est relativement simple, mais cela peut arriver dans d'autre cas). Il est toutefois possible de le faire en "supprimant" le type paramétré via un cast (toutefois cela produit quand même un warning préventif) :
1 2
| Iterator<Truc> iterT = null;
Iterator<Trucable> iter = (Iterator)iterT; // compile avec Warning "unchecked" |
Pourquoi ce principe d'invariance sur les générics (alors que cela est absent des tableaux par exemple) ? Tout simplement parce que toutes la vérification des Generics s'effectue à la compilation, et qu'il est donc impossible de vérifier le type exact de la référence dans ce cas là.
Ainsi si on prend le cas d'une liste d'Integer et d'une liste de Number (je rappele que Integer hérite de Number), le code suivant montre un cas où l'on passe outre les protections des générics (mais on a quand même un warning à la compilation) :
1 2 3 4 5 6 7 8 9 10 11
| List<Integer> listInteger = new ArrayList<Integer>();
List<Number> listNumber = (List) listInteger; // Warning "unchecked"
listNumber.add(new Double(0.0)); // Compile OK car Double hérite de Number aussi
// Par contre c'est une erreur car on ajoute un Double dans une List<Integer>
Integer i = listInteger.get(0); // Compile OK
// Mais générera une ClassCastException car la référence correspond à un Double
// --> On a casser les Generics :( |
Le pattern <? extends X> permet justement de gérer ce cas "proprement", afin de provoquer une erreur en cas de mauvaise utilisation :
1 2 3 4
| List<Integer> listInteger = new ArrayList<Integer>();
List<? extends Number> listNumber = listInteger; // Compile OK
listNumber.add(new Double(0.0)); // ERREUR (not applicable) !!! |
Bien sûr cela peut sembler complexe ou contraignant, mais cela permet d'être sûr que le code ne génèrera pas de ClassCastException...
Enfin, un compilateur "moins dépressif" ne pourra pas vérifier tous les cas, et ferait donc perdre l'unique intérêt des Generics : le code sécurisé !
En effet, si dans les exemples de mon post et de ton post la vérification est assez évidente, cela peut devenir nettement plus complexe (en particulier si l'implémentation est caché)
la vérification doit alors être fait à l'exécution.
Or avec les Generics le type paramétré est perdu à l'exécution (lire Les Generics ne sont pas des Templates comme les autres !).
Il y a bien des discussions pour conserver les types paramétrés à l'exécution (lire Reified Generics for Java) mais cela entrainera une incompatibilité avec les anciennes API... du coup il n'y a rien d'officiel pour le moment et je ne pense pas que cela sera le cas (en tout cas dans un future proche).
Quand à ton second exemple, il est un peu différent car le problème ne concerne pas directement les générics, mais il s'agit plutôt d'un problème d'ambiguité pour le compilateur...
En effet si on regarde la définition de la méthode Collections.unmodifiableList() :
public static <T> List<T> unmodifiableList(List<? extends T> list)
Or lorsqu'on écrit le code suivant :
1 2
| List<Truc> liste = new ArrayList<Truc>() ;
List<Trucable> lst = Collections.unmodifiableList(liste);// SE COMPILE PAS |
Cela ne compile pas car le compilateur utilise le type de liste pour paramétrer la méthode, ce qui nous donne T vaut Truc, et donc :
public static List<Truc> unmodifiableList(List<? extends Truc> list)
On se retrouve dans le même cas que dans ton premier exemple avec un problème de variance du type paramétré (le type de retour de la méthode est List<Truc> et on l'affecte à un List<Trucable>)...
Or si le compilateur utiliserait T vaut Trucable, cela fonctionnerait correctement car la signature devient alors :
public static List<Trucable> unmodifiableList(List<? extends Trucable> list)
Ainsi la troisième ligne que tu donnes en solution permet simplement d'indiquer au compilateur le type paramétré à utiliser en cas d'ambiguité :
List<Trucable> lst = Collections.<Trucable>unmodifiableList(liste ); // OK!!
Ce genre de problème peut également survenir avec des appels de méthodes sans Générics, par exemple avec ces deux méthodes :
1 2 3 4 5 6 7
| public static void method(Comparable c) {
System.out.println("Comparable : " + c);
}
public static void method(Number n) {
System.out.println("Number : " + n);
} |
Lorsque on compile le code suivant on obtiendra obligatoirement une erreur :
En effet le compilateur ne sait pas s'il doit appeler method(Comparable) ou method(Number) puisque le type Integer étend b]Number[/b] et implémente Comparable...
On est alors obligé de préciser en utilisant un cast :
method((Number)new Integer(9));
Pour conclure, je dirais qu'avec les Generics, le compilateur est devenu un peu plus chiant, mais c'est pour la bonne cause : un code plus sécurisé !
a++
Partager