Aller au contenu principal

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

LettreSignificationExemple
TTypeList<T>
EElementSet<E>
KKeyMap<K, V>
VValueMap<K, V>
NNumberStack<N>
RReturn typeFunction<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.

remarque

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

CollectionDéclarationInterprétation
HashSetHashSet<String>Ensemble de String
ArrayDequeArrayDeque<Integer>File de Integer
HashMapHashMap<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