Generics

En français, types paramétrés ; introduits en java 5.
Permettent de paramétrer classes, interfaces et méthodes par des types, comme les paramètres habituels des méthodes permettent de paramétrer par des valeurs.
Beaucoup utilisés pour les collections.
La syntaxe utilise < >

Par exemple java.util.List
Sans type paramétré, une List gère une collection de java.lang.object.
public interface List extends Collection{ ... }
Utilisation :
List l = new ArrayList();
l.add("toto");
Les méthodes pour gérer des éléments (add(), get() ...) manipulent des Object.
Avec les generics, on peut paramétrer par un type la création d'une liste :
public interface List<E> extends Collection<E>{ ... }
Permet de créer une liste dont les éléments sont de type E.
E représente un type non primitif.
List<String> l = new ArrayList<String>();
List<Integer> l = new ArrayList<Integer>();
Intérêts :

Generic types

(= types génériques)

Soit une classe Box qui encapsule un objet, avec deux méthodes, get() et set().
public class Box {

    private Object object;

    public void set(Object object) { this.object = object; }
    
    public Object get() { return object; }
}
Dans la version de la classe paramétrée par un type, T peut être utilisé dans tout le code de la classe :
    /**
     * Generic version of the Box class.
     * @param <T> the type of the value being boxed
     */
    public class Box<T> {

        private T t;
    
        public void set(T t) { this.t = t; }
        
        public T get() { return t; }
    }
A l'utilisation :
Box<String> b = new Box<String>();
b.set("hello");
String s = b.get();
Conventions pour les noms de types (utilisées dans l'API java) :
    E - Element
    K - Key
    N - Number
    R - Return
    T - Type
    V - Value
Exemples très nombreux dans l'api java :
java.util.Collection<E>
java.util.Map<K,​V>
java.util.stream.Stream<T>
    static <T> Stream<T> concat​(Stream<? extends T> a, Stream<? extends T> b)
    static <T> Stream<T> iterate​(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)
    <R> R collect​(Supplier<R> supplier, BiConsumer<R,​? super T> accumulator, BiConsumer<R,​R> combiner)

Inférence de type

Depuis java 7, il est possible de ne pas spécifier le type lorsqu'il peut être inféré (deviné) par le compilateur :
Box<String> b = new Box<String>;
peut être remplacé par :
Box<String> b = new Box<>;

Paramétrer avec plusieurs types

La syntaxe générale est <T1, T2, ..., Tn> (les Ti représentent des types).
public interface Pair<K, V> {
    public K getKey();
    public V getValue();
}

public class OrderedPair<K, V> implements Pair<K, V> {

    private K key;
    private V value;

    public OrderedPair(K key, V value) {
	this.key = key;
	this.value = value;
    }

    public K getKey()	{ return key; }
    public V getValue() { return value; }
}

// en utilisant l'inférence de type :
Pair<String, Integer> p1 = new OrderedPair<>("Even", 8);   // autoboxing de 8 vers un Integer
Pair<String, String>  p2 = new OrderedPair<>("hello", "world");
Voir par exemple dans l'API java.util.Map

Un type paramétré peut être lui-même paramétré :
OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));

Raw types

Si on a :
public class Box<T> {
    public void set(T t) { ... }
    // ...
}
A l'utilisation, on fait normalement :
Box<Integer> intBox = new Box<>();
Mais on peut faire aussi :
Box rawBox = new Box();
Dans ce cas, T vaut implicitement Object, et on parle du raw type (type brut) de Box<>();
Attention : un type non générique n'est pas un raw type.

L'utilisation de raw types génère des warnings à la compilation.
public class TestRawType1{
    public static void main(String[] args){
        List l = new ArrayList();
        l.add("hello");                    // ici warning compilation
        l.add(Integer.valueOf(3));         // ici warning compilation
        String s1 = (String) l.get(0);
        String s2 = (String) l.get(1);
    }
}
javac TestRawType1.java
Note: Main1.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
javac -Xlint:unchecked TestRawType1.java
TestRawType1.java:7: warning: [unchecked] unchecked call to add(E) as a member of the raw type List
        l.add("hello");
             ^
  where E is a type-variable:
    E extends Object declared in interface List

(code dans TestRawType1.java)

Pour désactiver les warnings, utiliser l'annotation @SuppressWarnings("unchecked")
@SuppressWarnings("unchecked")
public class TestRawType2{
    public static void main(String[] args){
        // ...
    }
}
(code dans TestRawType2.java)

Méthodes génériques

Méthode générique = méthode qui introduit ses propres types paramétrés, mais dont la portée est limitée à la méthode.
Meme syntaxe que pour la déclaration de types paramétrés.
Peut se trouver dans des méthodes statiques, d'instance ou des constructeurs.

Pour les méthodes statiques, les types doivent se trouver avant le type de retour.
class Util {
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
}

class Pair<K, V> {

    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public void setKey(K key) { this.key = key; }
    public void setValue(V value) { this.value = value; }
    public K getKey()   { return key; }
    public V getValue() { return value; }
}
Utilisation :
public class TestGenericMethods1{
    public static void main(String[] args){
        Pair<Integer, String> p1 = new Pair<>(1, "apple");
        Pair<Integer, String> p2 = new Pair<>(2, "pear");
        boolean same = Util.<Integer, String>compare(p1, p2);
        // Mais le compilateur est capable d'inférer le type, donc on peut faire :
        boolean same = Util.compare(p1, p2);
    }
}

(code dans TestGenericMethods1.java)

Bien voir la différence : dans la classe Pair<K, V>, les types K et V sont définis au niveau de la classe, donc sont connus par tout le code de cette classe.
La classe Util ne définit pas de type paramétré. Pour permettre à compare() d'utiliser des types paramétrés, on a besoin de paramétrer la méthode.

Bounded type parameters

( = types paramétrés limités)

extends signifie ici "extends" ou "implements" (même sémantique que l'annotation @Override).
public class BoundedTypeInGenericMethod{
    public static void main(String[] args) {
        Box<Integer> ib = new Box<>();
        ib.set(Integer.valueOf(10));
        // ib.inspect("some text"); // erreur de compilation
        ib.inspect(3.2); // autoboxing vers Double
    }
}

class Box<T> {
    private T t;          

    public void set(T t) { this.t = t; }

    public T get() { return t; }

    public <U extends Number> void inspect(U u){
        System.out.println("T: " + t.getClass().getName());
        System.out.println("U: " + u.getClass().getName());
    }
}

(code dans BoundedTypeInGenericMethod.java)

Intéressant : permet d'utiliser les méthodes du type limité :
public class NumberBox<T extends Integer> {

    private T n;

    public NumberBox(T n)  { this.n = n; }

    public boolean isEven() {
        return n.intValue() % 2 == 0;
    }
}
Dans isEven(), n est un objet de type Integer (ou d'une sous-classe), donc les méthodes de la classe Integer sont utilisables.

Exemple d'utilité de la clause extends :
public static <T> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
        if (e > elem)  // erreur de compilation
            ++count;
    return count;
}
Erreur de compilation car l'opérateur > ne s'applique pas aux objets en général, uniquement à certains types primitifs tels que int.
Pour pouvoir comparer des objets, on utilise l'interface java.lang.Comparable.
public static <T extends Comparable<T>> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
        if (e.compareTo(elem) > 0)
            ++count;
    return count;
}
(noter au passage que extends ici signifie implements)

Limites multiples

On peut aussi spécifier des limites multiples :
<T extends B1 & B2 & B3>
T doit être un sous-type de tous les types séparés par des &.
Cette syntaxe n'a de sens que pour spécifier des implements multiples.
Si un des Bi est une classe, doit être mis en premier.

Héritage

En java, on peut assigner des objets d'un type à des objets d'un autre type, si ces types sont compatibles :
Object someObject = new Object();
Integer someInteger = Integer.valueOf(10);
someObject = someInteger;   // OK
OK parceque Integer est un Object (qui peut le plus peut le moins).

Mais Integer est aussi un Number, donc on peut faire :
public void someMethod(Number n) { ... }

someMethod(Integer.valueOf(10));   // OK
someMethod(Double.valueOf(10.1));   // OK
Marche aussi avec les generics :
Box<Number> box = new Box<>();
box.add(Integer.valueOf(10));   // OK
box.add(Double.valueOf(10.1));  // OK
Mais ATTENTION, si on a la méthode :
public void boxTest(Box<Number> n) { ... }
Est-ce qu'on peut passer à cette méthode des Box<Integer> ou des Box<Double> ?

NON car Generics supbtype relationship

Sous-classage

On peut sous-classer une classe ou une interface générique avec les clauses extends et implements.
Exemple pour les sous-classes de java.util.Collection
List<E> extends Collection<E>
ArrayList<E> implements List<E>                                                      
Donc ArrayList<String> est un sous-type de List<String>, qui est un sous-type de Collection<String> Generics ArrayList hierarchy Si on définit notre propre interface pour gérer des listes :
(une liste où chaque élément est associé à un élément de type P)
interface PayloadList<E,P> extends List<E> {
  void setPayload(int index, P val);
}
(PayloadList ~ liste enrichie).

Tous ces paramétrages de PayloadList sont sous-classes de List<String> :
    PayloadList<String,String>
    PayloadList<String,Integer>
    PayloadList<String,Exception>
Generics PayloadList hierarchy

Wildcard

(wildcard = joker, comme * dans une expression régulière ou % en sql)
Ici, le caractère utilisé est ?
? représente un type inconnu à la compilation.

Upper bounded wildcard

(= wildcard avec limite supérieure)

Alors que
List<Number>
repésente uniquement une liste de type Number,
List<? extends Number>
représente une liste de Number ou de ses sous-classes, comme List<Integer> ou List<Double>
Donc List<? extends Number> est moins restrictif que List<Number>.

NOTE : ici, extends est pris au sens de extends ou implements.

Très utilisé dans l'API java, par exemple dans Interface java.util.Map<K,V>
void putAll(Map<? extends K,? extends V> m)
Dans certains cas, l'usage du wildcard peut etre évité :
import java.util.List;
import java.util.Arrays;
public class TestWildcard{

    public static void main(String[] args){
        List<Integer> li = Arrays.asList(1, 2, 3);
        System.out.println("sumOfList1 = " + sumOfList1(li));
        System.out.println("sumOfList2 = " + sumOfList2(li));
    }

    public static double sumOfList1(List<? extends Number> list) {
        double s = 0.0;
        for (Number n : list)
            s += n.doubleValue();
        return s;
    }
    
    public static <T extends Number> double sumOfList2(List<T> list) {
        double s = 0.0;
        for (Number n : list)
            s += n.doubleValue();
        return s;                                                                                                         
    }
    
}
(code dans TestWildcard.java)

On verra plus loin que si ces 2 méthodes donnent le même résultat, elles ne sont pas strictement équivalentes
car <? extends Number> et <T extends Number> sont de types différents, dans deux hiérarchies d'héritage différentes.

Unbounded wildcard

(= wildcard sans limite) ; par ex
List<?>
Si on veut afficher une liste de n'importe quel type,
public static void printList(List<Object> list) {
    for (Object elem : list)
        System.out.println(elem + " ");
    System.out.println();
}
ne permet pas d'afficher tous les types, par ex List<Integer> ou List<Double>, qui dérivent de Object mais pas de List<Object>.

Mais
public static void printList(List<?> list) { ... }
accepte une liste de n'importe quel type, comme List<Object>, List<Integer> ou List<Double>.
INCERTITUDE
printList() est l'exemple donné par Oracle pour justifier le besoin de unbounded wildcard.
Pourtant cette syntaxe fonctionne aussi :
public static void printList(List<? extends Object> list) { ... }
(code dans UnboundedOracle.java)
<?> est peut-être seulement un raccourci syntaxique de <? extends Object>.

Lower bounded wildcard

(= wildcard avec limite inférieure)
List<? super Integer>
permet de travailler avec des listes dont le type paramétré est une superclasse de Integer
Par exemple, pour écrire une méthode qui travaille sur des listes d'entiers et de tous ses supertypes (Number, Object) :
public static void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 10; i++) {
        list.add(i);
    }
}
List<? super Integer> est moins restrictif que List<Number>.

Wildcard et héritage

Rappel :
class A { ... }
class B extends A { ... }

// on peut tout à fait écrire :
B b = new B();
A a = b; // qui peut le plus (B) peut le moins (A)

// mais pas :
List<B> lb = new ArrayList<>();
List<A> la = lb;   // erreur de compilation
En effet, on a vu que List<B> n'est pas un sous-type de List<A>.

Les wildcards permettent de créer des relations d'héritage.
Déjà, on a la relation suivante : Generics - héritage avec wildcard Plus généralement, si B est un sous-type de A, < ? extends B> est un sous-type de < ? extends A>.
Par exemple : Generics - exemple d'héritage avec wildcard Donc dans l'exemple précédent, si on ne peut pas faire :
List<B> lb = new ArrayList<>();
List<A> la = lb;
on peut faire :
List<? extends B> lb = new ArrayList<>();
List<? extends A>  la = lb;
Exercice : Personnes
Exercice : Erreur de schéma
Exercice : Compare

Type erasure

A savoir : tous les types sont traduits à la compilation ; le code compilé se retrouve sans type générique.
La traduction n'est pas triviale, par exemple des casts sont rajoutés, des méthodes supplémentaires sont générées dans le cas d'utilisation de extends.