CodeGym /Cours /JAVA 25 SELF /Génériques : pourquoi ils sont nécessaires, syntaxe ...

Génériques : pourquoi ils sont nécessaires, syntaxe de base

JAVA 25 SELF
Niveau 26 , Leçon 4
Disponible

1. Le problème des collections « brutes » (raw types)

Un peu d’histoire. Avant Java 5, toutes les collections étaient « omnivores ». Elles stockaient des objets de type Object, et le compilateur ne contrôlait pas ce que vous y mettiez. Vous voulez y mettre une chaîne ? Pas de problème. Un nombre ? Pourquoi pas. Un chat ? C’est possible aussi.

// Exemple de collections « brutes » (raw types), Java avant la version 5
List list = new ArrayList();
list.add("Bonjour");
list.add(42);
list.add(new Object());

Le problème apparaissait au moment de « récupérer » et d’utiliser la valeur :

String s = (String) list.get(0); // OK, c'est une chaîne
String s2 = (String) list.get(1); // BOUM ! ClassCastException

Le compilateur se tait, et à l’exécution vous obtenez une ClassCastException. C’est comme une boîte portant l’étiquette « pommes », dans laquelle se trouvent une tasse, une banane et un hérisson.

Pourquoi est-ce mauvais ?

  • Les erreurs n’apparaissent qu’à l’exécution.
  • Les types se mélangent : vous devez convertir (caster) manuellement les objets vers le type voulu.
  • Le code est moins lisible et plus risqué.

La solution — les génériques

Les generics (génériques) sont un mécanisme permettant de créer des classes, interfaces et méthodes avec des paramètres de type. Autrement dit, vous dites à la collection : « Ne stocke que des chaînes », et le compilateur s’en assure strictement.

List<String> words = new ArrayList<>();
words.add("Bonjour");
words.add("Monde");
// words.add(42); // Erreur de compilation ! Impossible d'ajouter un int dans List<String>

Désormais, le compilateur n’autorisera rien d’autre que des chaînes dans la liste. L’erreur sera détectée avant le lancement du programme.

Idée principale des génériques :
Assurer la sûreté de types des collections (et pas seulement), pour que les erreurs soient détectées à la compilation et non à l’exécution.

2. Syntaxe des génériques : à quoi ça ressemble dans le code

Indiquer le type entre chevrons

Quand vous créez une collection, indiquez le type des éléments entre <> :

List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
// names.add(123); // Erreur : impossible d'ajouter un nombre dans une liste de chaînes

String first = names.get(0); // Pas besoin de cast !

Classiques :

  • List<String> — liste de chaînes
  • List<Integer> — liste d’entiers
  • Set<Double> — ensemble de nombres à virgule flottante
  • Map<String, Integer> — clé String, valeur Integer

Pourquoi ne pas écrire simplement List ?

On peut, mais vous perdez tous les avantages des génériques, et le compilateur vous avertira :

List list = new ArrayList(); // raw type — déconseillé !
list.add("Hello");
list.add(7.5);
String s = (String) list.get(1); // Bonjour, ClassCastException !

Le code Java moderne utilise toujours les génériques.

Opérateur diamant <>

Depuis Java 7, vous pouvez ne pas indiquer le type à droite s’il est clair d’après le contexte :

List<String> list = new ArrayList<>(); // Le compilateur déduira qu'il s'agit de <String>

3. Génériques pour différentes collections

Exemples pour List, Set, Map

List<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);

Set<String> uniqueNames = new HashSet<>();
uniqueNames.add("Alice");
uniqueNames.add("Bob");

Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 23);
ages.put("Bob", 31);

Exemple avec une classe personnalisée

class Student {
    String name;
    int age;
    // ...
}

List<Student> students = new ArrayList<>();
students.add(new Student());

4. Points utiles

Avantages des génériques

Sûreté de types. Le compilateur veille à ce que seules des valeurs du type attendu soient placées dans la collection.

Plus besoin de conversions de type. Avant : String s = (String) list.get(0);. Maintenant : String s = list.get(0);.

Code plus lisible et plus fiable. Moins de surprises à l’exécution.

Limitations des génériques

Impossible d’utiliser des types primitifs. Les génériques ne fonctionnent qu’avec des objets, pas avec des primitifs (int, double, boolean). Utilisez les classes enveloppes : Integer, Double, Boolean.

List<Integer> numbers = new ArrayList<>();
numbers.add(10); // int est automatiquement converti en Integer (autoboxing)

En bref sur l’effacement de types (type erasure)

En Java, les génériques sont implémentés via l’effacement de types : après compilation, l’information sur les paramètres de type est effacée, et à l’exécution la JVM ne sait pas qu’il s’agissait d’un List<String> plutôt que d’un simple List. C’est fait pour la rétrocompatibilité.

Conséquence : impossible de vérifier un paramètre de type via instanceof avec un argument de type concret.

List<String> list = new ArrayList<>();
// if (list instanceof List<String>) { ... } // Erreur de compilation !

Tenter d’ajouter un élément d’un autre type — erreur de compilation

List<String> words = new ArrayList<>();
words.add("Hello");
// words.add(123); // Erreur de compilation : incompatible types: int cannot be converted to String

Map<String, Integer> map = new HashMap<>();
map.put("Chat", 5);
// map.put(3, "Éléphant"); // Erreur : la clé doit être String, la valeur — Integer

Et c’est très bien : les erreurs sont détectées à la compilation.

Pas seulement les collections

Vous pouvez utiliser les génériques dans vos propres classes et méthodes. Par exemple, une « Boîte » universelle :

class Box<T> {
    private T value;

    public void set(T value) { this.value = value; }
    public T get() { return value; }
}

Box<String> stringBox = new Box<>();
stringBox.set("Bonjour");
System.out.println(stringBox.get());

Box<Integer> intBox = new Box<>();
intBox.set(42);
System.out.println(intBox.get());

Dans les collections, les génériques sont la norme, mais vous les rencontrerez aussi ailleurs, par exemple dans le Stream API et Optional.

5. Erreurs typiques avec les génériques

Erreur n° 1 : Utiliser des collections « brutes ». Une écriture du type List list = new ArrayList(); supprime la sûreté de types. Indiquez toujours des paramètres de type, par exemple List<String>.

Erreur n° 2 : Tenter d’utiliser des primitifs. On ne peut pas écrire List<int>, utilisez List<Integer>.

Erreur n° 3 : Conversion manuelle lors de la lecture depuis une collection. Si vous utilisez des génériques, la conversion (String) list.get(i) est inutile. Si vous en avez besoin, c’est qu’il y a eu une rupture de types quelque part.

Erreur n° 4 : S’attendre à ce que les paramètres de type soient disponibles à l’exécution. À cause de l’effacement de types, on ne peut pas les vérifier via instanceof comme pour List<String>.

Erreur n° 5 : Mélanger différents types dans une même collection. Si vous avez déclaré List<String>, n’essayez pas d’ajouter un Integer — le compilateur le refusera, et c’est une bonne chose.

1
Étude/Quiz
Collections et generics, niveau 26, leçon 4
Indisponible
Collections et generics
Collections et generics
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION