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; }
}