Explication des Generics en Java

Les génériques (generics) sont un mécanisme qui nous permet d’écrire du code qui ne se soucie pas du type des objets qu’il manipule tout en donnant suffisamment d’informations au compilateur pour préserver la sécurité des types.

Il ne faut pas utiliser un objet pour représenter n’importe quel type tout le temps.

Commençons par l’exemple classique d’une liste.
En oubliant un instant que List et LinkedList sont des types génériques, ce qui suit est “légal” en Java et crée une liste contenant tout type d’objet :

List maListe = new LinkedList();


Ajouter ces éléments à la liste semble “légal” aussi :

maListe.add("Unicorn");
maListe.add(10);
System.out.println(maListe); // Output : [Unicorn, 10]


Ce mélange de types semble bizarre, est-ce qu’on peut également écrire la boucle suivante ?

for (Integer i : maListe) {
     System.out.println(i);
}
// Output : java: incompatible types: java.lang.Object cannot be converted to java.lang.Integer


Non. Comme la liste ne porte pas d’information sur le type de ses éléments, le compilateur ne peut pas vérifier si chaque affectation à la variable i est valide. Au lieu de cela, il faut écrire :

for (Object i : maListe) {
     System.out.println(i);
}

/* 
Output : Unicorn 10
*/


Si nous avons l’intention de traiter chaque élément de la liste comme un entier afin d’effectuer une opération arithmétique, les choses se gâtent :

for (Object i : maListe) {
     System.out.println(((Integer) i) * 2);
}

/* Output :
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
	at ListTest.main(ListTest.java:15)
*/


En fait, c’est devenu si moche que tout élément qui n’est pas un entier fera planter l’application avec une ClassCastException. C’est le problème lorsque l’on utilise Object pour représenter un type inconnu : le code devient “fragile” lorsqu’une hypothèse est faite sur ce que ce type représente.


Résolution du problème par l’utilisation de paramètres de type

Lorsque nous profitons du type générique et écrivons ce qui suit à la place, ce problème est résolu car le compilateur peut maintenant faire toutes les vérifications pour nous :

List<Integer> maListe = new LinkedList<Integer>();
maListe.add(10);
maListe.add("Unicorn"); // Ligne devenue illégale

for (Integer i : maListe) {
     System.out.println(i * 2);
}


Maintenant, nous pouvons être sûrs que la liste ne contient rien d’autre que des entiers.

Un paramètre de type est, avant tout, une instruction pour le compilateur plus que pour le développeur.

interface maCollection<T> {
   void add (T element);
}


Lorsque nous déclarons une interface telle que celle ci-dessus, nous disons au compilateur qu’il doit s’assurer que l’argument de la méthode add() sera toujours du type spécifié par le type (T). Quel que soit le type, cela nous importe peu, du moment que c’est toujours le même.

Quand le type générique doit avoir une sémantique standard

Il est possible d’imposer des restrictions aux déclarations de types génériques en utilisant le mot-clé “extends” pour exposer une interface connue. De cette façon, le type générique devient significatif et utilisable au sein de la méthode ou de la classe.
Tout type, quel qu’il soit, peut être utilisé à la place du type générique, pour autant qu’il implémente l’interface spécifiée :

class MyClass<T extends MonAutreInterface> {
   // Utilisation de T d'une manière ou d'une autre
}


Conclusion

Les types génériques en Java nous permettent d’écrire du code qui peut manipuler différents types d’objets sans savoir quels sont ces types exacts tout en maintenant la sécurité des types.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Retour en haut
%d blogueurs aiment cette page :