Comprendre les Génériques en Java

L'objectif principal est de restreindre une collection ou une structure à un type précis, offrant ainsi une vérification de type dès la compilation plutôt qu'à l'exécution. À noter que les méthodes statiques d'une classe générique ne peuvent exploiter les paramètres de type déclarés au niveau de la classe elle-même.

Héritage et classes génériques

Lorsqu'une sous-classe hérite d'une classe parent générique, plusieurs scénarios sont possibles :

Spécification explicite du type : la sous-classe n'est plus générique.

public class CommandeSpeciale extends Commande<Integer> {
    // CommandeSpeciale n'est pas une classe générique
}

Conservation du paramètre de type : la sous-classe demeure générique.

public class CommandeGenerique<T> extends Commande<T> {
    // CommandeGenerique reste paramétrable
}

Illustration complète avec un parent à deux paramètres :

class Parent<K, V> { }

// Effacement total des types
class FilsA extends Parent { 
    // équivaut à Parent<Object, Object> 
}

// Fixation des deux types
class FilsB extends Parent<Integer, String> { }

// Conservation intégrale
class FilsC<K, V> extends Parent<K, V> { }

// Conservation partielle
class FilsD<V> extends Parent<Integer, V> { }

Méthodes génériques

Une méthode générique déclare son propre paramètre de type, indépendant de ceux de sa classe conteneur. Sa déclaration se place avant le type de retour. Contrairement aux variables d'instance génériques, les méthodes génériques peuvent être statiques car leur type est résolu lors de l'appel.

public static <T> List<T> transformerTableauEnListe(T[] tableau) {
    List<T> resultat = new ArrayList<>();
    for (T element : tableau) {
        resultat.add(element);
    }
    return resultat;
}

Le wildcard (joker) ?

Le symbole ? représente un type inconnu. Il sert de dénominateur commun entre différentes instanciations d'un même type générique. Par exemple, bien que List<Object> et List<String> soient sans relation de parenté directe, List<?> fait office de supertype commun. Une collection ainsi typée accepte la lecture (type de retour Object) mais refuse toute insertion, à l'exception de null.

@Test
public void demonstrationWildcard() {
    List<Object> objets = null;
    List<String> chaines = null;

    List<?> listeGenerique = null;
    listeGenerique = objets;   // valide
    listeGenerique = chaines;  // valide

    List<String> noms = new ArrayList<>();
    noms.add("Alice");
    noms.add("Bob");

    listeGenerique = noms;

    // Insertion interdite sauf null
    listeGenerique.add(null);
    // listeGenerique.add("Test"); // erreur de compilation

    // Lecture autorisée, type de retour : Object
    Object premier = listeGenerique.get(0);
    System.out.println("Taille : " + listeGenerique.size());
    System.out.println(premier);
}

Wildcards bornés

Borne supérieure (? extends T) : accepte T ou ses sous-types. La lecture renvoie un élément de type T, mais toute insertion est interdite car le type exact ne peut être garanti.

Borne inférieure (? super T) : accepte T ou ses super-types. L'insertion d'éléments de type T ou de ses sous-types est autorisée, mais la lecture renvoie Object.

@Test
public void demonstrationBornes() {
    List<? extends Personne> limiteHaute = null;
    List<? super Personne> limiteBasse = null;

    List<Etudiant> etudiants = new ArrayList<>();
    List<Personne> personnes = new ArrayList<>();
    List<Object> objets = new ArrayList<>();

    // ? extends Personne : accepte Etudiant et Personne
    limiteHaute = etudiants;
    limiteHaute = personnes;
    // limiteHaute = objets; // refusé

    // ? super Personne : accepte Personne et Object
    // limiteBasse = etudiants; // refusé
    limiteBasse = personnes;
    limiteBasse = objets;

    // Lecture avec borne supérieure
    limiteHaute = etudiants;
    Personne p = limiteHaute.get(0);
    // Etudiant e = limiteHaute.get(0); // échec à la compilation

    // Lecture avec borne inférieure
    limiteBasse = personnes;
    Object o = limiteBasse.get(0);
    // Personne p2 = limiteBasse.get(0); // échec à la compilation

    // Écriture avec borne supérieure : interdite
    // limiteHaute.add(new Etudiant()); // erreur

    // Écriture avec borne inférieure : autorisée pour les sous-types
    limiteBasse.add(new Personne());
    limiteBasse.add(new Etudiant());
}

Étiquettes: Java génériques collections Wildcard type safety

Publié le 31 juillet à 02h42