Génériques (Templates)
Introduction
Les génériques (Generics) permettent de créer des classes, interfaces et méthodes qui fonctionnent avec différents types tout en maintenant la sécurité du typage à la compilation. Ils éliminent le besoin de casts explicites et détectent les erreurs de type dès la compilation plutôt qu'à l'exécution.
Pourquoi les génériques?
Sans génériques (avant Java 5):
ArrayList liste = new ArrayList();
liste.add("Bonjour");
liste.add(42); // Accepté - problème!
String s = (String) liste.get(0); // Cast nécessaire
String s2 = (String) liste.get(1); // ClassCastException à l'exécution!
Avec génériques:
ArrayList<String> liste = new ArrayList<>();
liste.add("Bonjour");
liste.add(42); // MAUVAIS - Erreur de compilation - type incompatible
String s = liste.get(0); // Pas de cast nécessaire
Syntaxe de base
Paramètres de type
Les paramètres de type sont notés entre chevrons < >:
ArrayList<String> listeNoms = new ArrayList<>();
HashMap<Integer, String> mapUtilisateurs = new HashMap<>();
HashSet<Double> ensembleNombres = new HashSet<>();
Conventions de nommage
| Lettre | Signification | Exemple |
|---|---|---|
T | Type | List<T> |
E | Element | Set<E> |
K | Key | Map<K, V> |
V | Value | Map<K, V> |
N | Number | Stack<N> |
R | Return type | Function<T, R> |
Déclarer une classe générique
Pour créer votre propre classe générique, on place le paramètre de type <E> après le nom de la classe. Ce E devient alors utilisable partout dans la classe comme si c'était un type concret.
Exemple : une liste simple inspirée d'ArrayList<E>
public class MaListe<E> {
private Object[] elements;
private int taille;
public MaListe() {
elements = new Object[10];
taille = 0;
}
// Ajouter un élément de type E
public void ajouter(E element) {
elements[taille] = element;
taille++;
}
// Récupérer un élément — le cast est nécessaire en interne, mais invisible de l'extérieur
@SuppressWarnings("unchecked")
public E obtenir(int index) {
return (E) elements[index];
}
public int taille() {
return taille;
}
}
Utilisation
MaListe<String> noms = new MaListe<>();
noms.ajouter("Alice");
noms.ajouter("Bob");
String premier = noms.obtenir(0); // "Alice" — pas de cast nécessaire
MaListe<Integer> notes = new MaListe<>();
notes.ajouter(85);
notes.ajouter(92);
int note = notes.obtenir(1); // 92
Le type E est remplacé par le type réel (String, Integer, etc.) au moment de la compilation. C'est exactement comme ça qu'ArrayList<E> est codé dans Java.
ArrayList<E> de Java fait la même chose en interne : un tableau d'Object[] avec des casts gérés à l'intérieur de la classe. Le générique sert à garantir la sécurité du type à l'extérieur de la classe.
Lire la syntaxe des collections Java
Toutes les collections Java utilisent les génériques. Voici comment lire leurs déclarations :
Un seul paramètre de type
HashSet<E> et ArrayDeque<E> n'ont un seul paramètre : le type des éléments stockés.
HashSet<String> prenoms = new HashSet<>(); // un ensemble de String
HashSet<Integer> notes = new HashSet<>(); // un ensemble d'Integer
ArrayDeque<String> file = new ArrayDeque<>(); // une file de String
Deux paramètres de type
HashMap<K, V> a deux paramètres : le type de la clé et le type de la valeur.
HashMap<String, Integer> ages = new HashMap<>();
// ^^^^^^ ^^^^^^^
// K V
// clé valeur
ages.put("Alice", 25); // clé = "Alice", valeur = 25
ages.put("Bob", 30);
int age = ages.get("Alice"); // retourne 25
Résumé visuel
| Collection | Déclaration | Interprétation |
|---|---|---|
HashSet | HashSet<String> | Ensemble de String |
ArrayDeque | ArrayDeque<Integer> | File de Integer |
HashMap | HashMap<String, Integer> | Clé String → Valeur Integer |
Bonnes pratiques essentielles
Ne pas utiliser de raw types (sans paramètre de type) :
// MAUVAIS
List liste = new ArrayList();
// BON
List<String> liste = new ArrayList<>();
Utiliser le diamond operator (pas besoin de répéter le type à droite) :
// MAUVAIS - redondant
List<String> liste = new ArrayList<String>();
// BON
List<String> liste = new ArrayList<>();
Résumé
- Les génériques apportent la sécurité du typage à la compilation
<T>représente un type paramétré — la lettre est arbitraire, c'est une convention- Toujours utiliser les génériques avec les collections pour éviter les bugs
- Permet d'éviter les
ClassCastExceptionà l'exécution