Un type générique n’est pas un type. Un type générique permet de fabriquer des types.

List<Integer> l = new ArrayList<>(); // enfaite, c'est new ArrayList<Integer>()
// ou var l = new ArrayList<Integer>(); où l est un ArrayList<Integer>
l.add(5); // enfaite, c'est l.add(Integer.valueOf(5));

Integer est un wrapper pour le type int (limitation de Java)

int \ra Integer : Boxing
Integer \ra int : Unboxing

Il est possible de rendre générique n’importe quel type (record/interface/classe).

class Wrapper<T> {
  private T value;
  public Wrapper(T v) { this.value = v; }
  public T getValue() { return value; }
}

Étude de cas : On veut créer un parking de Véhicules global, parfois aussi un parking de voitures uniquement
Avoir un Parking<T> permet également de créer un parking d’autre chose, comme un Parking de Integer. Il faut donc mettre une contrainte sur le type d’instanciation, en faisant Parking<T extends Vehicule>.

Habituellement, A extends B : A est une sous-classe stricte de B.
Ici, extends n’est pas stricte. Vehicule est son propre sous-type.

Si on veut une contrainte plus spécifique, comme Voiture ou Moto, il faut une classe scellée scealed class (classe scellée = limité l’héritage) \RA donc il faudrait une classe intermédiaire scellée.

Si on veut ajouter des contraintes, on peut faire class Wrapper<T extends Number & Serializable>. Note que Serializable est une interface, on ne peut pas mettre deux classes, seulement une classe (à gauche) et ensuite des interfaces (ici, on a Serializable).

Si on veut mettre n’importe quel type, on peut utiliser ?.

class Inventaire {
  List<List<?>> inventaire;
  void add(List<?> l) { inventaire.add(l); }
}

Sauf que là, c’est trop n’importe quoi, alors on peut rajouter une contrainte à ce joker.

class Inventaire<T as ? extends Livre> { // classe générique
  List<List<? extends Livre>> inventaire; // créer des données, extends
  // Sous-type de Livre
  void add(List<? extends Livre> l) { inventaire.add(l); } // créer des données, extends
  
  private List<Livre> lo;
  public void fill(List<? super Livre l) { for (var livre: lo) l.add(livre); } // consomme des données, super
}

Pour “ouvrir par le haut”, on peut faire <? super Livre>.

Principe PECS (Producer = Extends, Consumer = Super)

Méthode générique

public class Main { // méthode générique
  static <T> T f(T t) { return t; }
}

Introduction des paramètres génériques : <T>.
En général, pas besoin de préciser le type lors de l’appel de la méthode

f(3); // ça fonctionne, via l'inférence de type
// compris comme ça :
f(Integer.valueOf(3)); // rappel : pas de type primitif, donc int est transformé en Integer

// On peut quand même préciser le type.
Main.<Object>f(3);

On peut évidemment avoir des méthodes génériques dans des classes génériques.

class K<T> {
  <U extends T> U f(U u, T t) { return u; }

  // Le type de paramètre T cache le type T de la classe
  // ça compile mais c'est crade
  <T> T f(T t) { return t; }
}