Génériques

Transtypage

List strings = Arrays.asList("One", "Two", "Three");
Object stringObject = strings.get(0);
String string = (String) stringObject;

List ints = Arrays.asList(1, 2, 3);
Object intObject = ints.get(0);
int number = (int) intObject;

Problème

  • Le développeur assume la responsabilité de la compilation

    • Possibilité d’exception à l’exécution

  • Comment améliorer la vérification de type à la compilation ?

Types génériques

  • Depuis Java 5

  • Implémentation des classes template en UML

  • Contraint les types

Utilisation des génériques

  • Contrainte de type indiquée entre chevrons

    • Obligatoire pour la déclaration

    • Optionnel pour l’instanciation (Java 7)

// Avant Java 7
List<String> strings = new ArrayList<String>();

// Depuis Java 7
List<String> strings = new ArrayList<>();

Code re-travaillé

List<String> strings = Arrays.asList("One", "Two", "Three");
String string = strings.get(0);

List<Integer> ints = Arrays.asList(1, 2, 3);
int number = ints.get(0);

Type erasure

  • Vérification à la compilation

  • Aucune différence dans le bytecode

interface Foo {
    void bar(List<String> strings);
    void bar(List<Integer> integers); // compilation error
}

Création de classe générique

interface Foo<T> {
    void apply(T t);
}

Sous-classe

class Bar1 implements Foo<String> {
    public void apply(String string) {}
}

class Bar2<T> implements Foo<T> {
    public void apply(T t) {}
}

Problème de variance

Integer[] integers = { 1, 2, 3, 4 };
Number[] numbers = integers;
numbers[0] = 3.14; // ArrayStoreException

Solution avec les génériques

List<Integer> integers = Arrays.asList(0, 1, 2, 4);
List<Number> numbers = integers; //compiler error

Co-variance

  • Utilisation d’un type et de ses sous-types

  • Permet de lire

  • Mais pas d’écrire !

Caractère joker

? indique un caractère joker

Sous-type de …​Notation

T

<? extends T>

Object

<?>

List<Integer> integers = Arrays.asList(0, 1, 2, 4);
List<? extends Number> numbers = integers;
int zero = integers.get(0);
Number one = numbers.get(1);
integers.add(5);
numbers.add(6); // compiler error

Contra-variance

  • Utilisation d’un type et de ses super-types

  • Permet d’écrire

  • Mais pas de lire !

List<Number> numbers = Arrays.asList(0, 1, 2, 4);
List<? super Integer> integers = numbers;
integers.add(5);
numbers.add(6);
Number one = numbers.get(1);
integers.get(0); // Type Object

Méthode générique

public class Pair<K, V> {

    private final K key;
    private final V value;

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

    private final K key;
    private final V value;

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

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

Contrainte de type de paramètre

  • Contrainte sur un type dans la signature d’une méthode

  • Sans que la classe soit générique

  • Via un paramètre supplémentaire dans la signature

Exemple de contrainte simple

public class Foo {
    static <T> T id(T t) {
        return t;
    }

    public static void main(String... args) {
        Foo foo = new Foo();
        String string = Foo.id("Hello");
    }
}

Autre exemple de contrainte simple

public class Foo {
    static <T extends Number> T max(T a, T b) {
        if (a.doubleValue() > b.doubleValue()) {
            return a;
        }
        return b;
    }

    public static void main(String... args) {
        int max1 = Foo.max(1, 2);
        double max2 = Foo.max(1.0, 2.0);
        Number max = Foo.max(1, 2.0);
    }
}

Exemple de contrainte complexe

public class Collections {

    public static <T> void copy(List<? super T> dest,
                                List<? extends T> src) {
        ...
    }
}

List<? super Integer> dest = new ArrayList<>();
List<? extends Integer> src = Arrays.asList(0, 1, 2, 4);
Collections.copy(dest, src);