Generics en Java : pourquoi en avons-nous besoin et comment les utiliser
Les Generics (génériques) sont l'une des fonctionnalités clés du langage Java, apparue dans la version J2SE 5.0. Ils permettent de créer des classes, des interfaces et des méthodes qui fonctionnent avec différents types de données, tout en conservant une typage strict au moment de la compilation. Dans cet article, nous allons voir pourquoi les génériques sont nécessaires, comment les utiliser correctement et quelles erreurs ils aident à éviter.
Pourquoi avons-nous besoin de Generics en Java ?
Avant l'apparition des génériques, les programmeurs utilisaient le casting (conversion de type) et travaillaient avec Object. Cela entraînait deux problèmes principaux :
- Absence de sécurité de type — on pouvait ajouter un objet de n'importe quel type à une collection, et l'erreur n'apparaissait qu'à l'exécution.
- Nécessité d'un casting explicite — lors de l'extraction d'un élément d'une collection, un cast était requis, ce qui alourdissait le code.
// Sans Generics (Java avant 5)List list = new ArrayList();list.add("chaîne");list.add(123); // Pas d'erreur à la compilationString s = (String) list.get(0); // Il faut caster explicitement// Avec GenericsList<String> list = new ArrayList<>();list.add("chaîne");// list.add(123); // Erreur de compilation !String s = list.get(0); // Pas besoin de castLes principaux avantages des Generics :
- Sécurité de type — les erreurs de type sont détectées à la compilation.
- Élimination des casts explicites — le code devient plus propre et plus lisible.
- Réutilisation du code — une seule implémentation fonctionne avec différents types.
Création de classes et méthodes génériques
Classe générique
Pour créer une classe qui fonctionne avec un type quelconque, il suffit d'indiquer le paramètre de type entre chevrons après le nom de la classe :
public class Box<T> { private T content;
public void setContent(T content) { this.content = content; }
public T getContent() { return content; }
public static void main(String[] args) { Box<String> stringBox = new Box<>(); stringBox.setContent("Bonjour, Java !"); System.out.println(stringBox.getContent());
Box<Integer> intBox = new Box<>(); intBox.setContent(42); System.out.println(intBox.getContent()); }}Dans cet exemple, T est le paramètre de type. Lors de la création d'un objet Box<String>, toutes les occurrences de T sont remplacées par String.
Méthode générique
Les méthodes peuvent également être paramétrées. Le paramètre de type est indiqué avant le type de retour :
public class Util { public static <T> void printArray(T[] array) { for (T element : array) { System.out.print(element + " "); } System.out.println(); }
public static void main(String[] args) { Integer[] intArray = {1, 2, 3, 4, 5}; String[] strArray = {"A", "B", "C"};
printArray(intArray); // 1 2 3 4 5 printArray(strArray); // A B C }}Notez que le paramètre de type <T> se trouve avant void. C'est obligatoire pour les méthodes génériques.
Types bornés (Bounded Type Parameters)
Parfois, il est nécessaire de limiter les types pouvant être utilisés comme paramètre. Pour cela, on utilise les mots-clés extends et super.
Borne supérieure (extends)
Limite le type par le haut — seuls la classe spécifiée elle-même ou ses sous-classes peuvent être utilisées :
public class Calculator<T extends Number> { private T value;
public Calculator(T value) { this.value = value; }
public double square() { return va