Introduction : Le problème que résolvent les Generics
Imaginez que vous écrivez une méthode pour trier une liste de nombres. Vous l'écrivez pour Integer, et une semaine plus tard, vous devez trier une liste de String. Vous devez créer une nouvelle méthode ou utiliser Object et faire constamment des conversions de types. C'est peu pratique, dangereux et conduit à des erreurs lors de l'exécution.
C'est exactement ce problème que résolvent les Generics (génériques) en Java. 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 lors de la compilation. Sans les génériques, le Java moderne serait beaucoup moins pratique et sûr.
Dans cet article, nous verrons pourquoi les Generics sont nécessaires, comment ils fonctionnent et, surtout, comment les appliquer avec des exemples concrets.
1. Bases des Generics : ce que c'est et comment ça fonctionne
1.1. Le problème des types "bruts" (raw types)
Avant l'arrivée de Java 5 (2004), les développeurs utilisaient des collections sans spécifier le type :
List list = new ArrayList();list.add("Bonjour");list.add(123);// Lors de la récupération, il faut se souvenir du typeString text = (String) list.get(0); // OKInteger num = (Integer) list.get(1); // OK// Mais si on se trompe — ClassCastException à l'exécutionString error = (String) list.get(1); // Erreur !Ce code est dangereux : l'erreur n'est détectée qu'au lancement du programme.
1.2. Comment fonctionnent les Generics
Les Generics permettent de spécifier le type de données avec lequel une classe ou une méthode va travailler, directement entre chevrons <T>. Le compilateur vérifie les types lors de la compilation et insère automatiquement les conversions correctes.
Syntaxe de base :
// Déclaration d'une classe génériquepublic class Box<T> { private T content; public void setContent(T content) { this.content = content; } public T getContent() { return content; }}
// UtilisationBox<String> stringBox = new Box<>(); // <> — opérateur diamant (Java 7+)stringBox.setContent("Bonjour");String text = stringBox.getContent(); // Sans conversion !
Box<Integer> intBox = new Box<>();intBox.setContent(42);int number = intBox.getContent(); // Sans conversion !
// stringBox.setContent(123); // Erreur de compilation !1.3. Avantages en pratique
- Sécurité des types : les erreurs sont détectées à la compilation, pas à l'exécution.
- Suppression des conversions explicites : le code devient plus propre et plus lisible.
- Réutilisation du code : une classe/méthode fonctionne avec n'importe quel type.
2. Les Generics en pratique : classes, méthodes et interfaces
2.1. Classes génériques
Le cas le plus courant est celui des collections. Mais vous pouvez créer vos propres classes génériques :
public class Pair<K, V> { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; }}
// UtilisationPair<String, Integer> pair = new Pair<>("Âge", 30);String key = pair.getKey();Integer value = pair.getValue();2.2. Méthodes génériques
Une méthode peut avoir ses propres paramètres de type, indépendants de la classe :
public class Utils { // Méthode générique pour créer une liste à partir d'un tableau public static <T> List<T> arrayToList(T[] array) { List<T> list = new ArrayList<>(); for (T element : array) { list.add(element); } return list; }}
// UtilisationString[] names = {"Anna", "Boris", "Victor"};List<String> nameList = Utils.arrayToList(names);