Version without JavaScript, read-only: workshops, quizzes and running code are not available here. Full version · Catalogue · Sign in

Lecture notes · chapter 12

Generics and enumerations

Two typing tools that make it possible to detect some errors at compile time rather than at run time. A chapter to read, not certified.

Enumerations

The problem with constants

public static final int ROUGE = 1;
public static final int BLEU  = 2;
public static final int JAUNE = 3;

int couleur = ROUGE;
couleur = 42;              // perfectly legal, and perfectly absurd

These constants offer no type safety and are not grouped together: nothing prevents the use of a value that does not exist. The problem is the same with strings: between "chapeau" and "chapaeu", a typo is enough to create a bug.

public enum Couleur { ROUGE, BLEU, JAUNE }

Couleur c = Couleur.ROUGE;    // and nothing else is acceptable

An enumeration can carry behaviour

Unlike a simple list of constants, an enumeration can associate data and methods with its values.

Before running the program, try to predict the price of a legendary duck with a base of 100 cents, and what the switch does on a case that is not handled.

Rarete.java

enum Rarete {

    COMMUN(1), RARE(5), LEGENDAIRE(50);

    private final int multiplicateur;

    Rarete(int multiplicateur) { this.multiplicateur = multiplicateur; }

    public int appliquer(int prixBase) { return prixBase * multiplicateur; }
}

enum Accessoire { CHAPEAU, CAPE, BOUEE }

class Demo {

    static String commenter(Accessoire a) {
        switch (a) {
            case CHAPEAU: return "stylé";
            case CAPE:    return "super-héros";
            case BOUEE:   return "prêt pour la piscine";
        }
        // Unreachable as long as the enumeration does not change.
        throw new IllegalStateException("accessoire non traité : " + a);
    }

    public static void main(String[] args) {

        for (Rarete r : Rarete.values()) {
            System.out.println(r + " : " + r.appliquer(100) + " centimes"
                             + "   (position " + r.ordinal() + ")");
        }

        System.out.println();
        for (Accessoire a : Accessoire.values()) {
            System.out.println(a + " : " + commenter(a));
        }

        System.out.println();
        System.out.println("depuis une chaîne : " + Rarete.valueOf("RARE").appliquer(10));
    }
}

A workshop goes here: you write and run code in it. It requires JavaScript.

values() gives all the values, ordinal() the position, valueOf does the reverse of toString. An enumeration is a class in its own right: it can have fields, a constructor and methods.

Generics

The problem with raw types

List bassin = new ArrayList();
bassin.add(new CanardEnPlastique("Jean", 200));
bassin.add("pas un canard...");           // accepted

CanardEnPlastique c = (CanardEnPlastique) bassin.get(1);   // ClassCastException

A cast is needed on every read, and the error only shows up at run time, far from the line that caused it.

List<CanardEnPlastique> bassin = new ArrayList<CanardEnPlastique>();
bassin.add(new CanardEnPlastique("Jean", 200));
// bassin.add("PAS UN CANARD");           rejected at compile time

CanardEnPlastique c = bassin.get(0);      // no cast

Invariance

List<String> noms = new ArrayList<String>();
List<Object> objets = noms;               // FORBIDDEN

Yet a String is an Object. The compiler rejects this assignment because, if it were allowed, one could write:

objets.add(new CanardEnPlastique("Faux", 0));
String s = noms.get(0);                   // ClassCastException, and nobody cheated

So List<String> is not a subtype of List<Object>. Generics are said to be invariant.

Wildcards

For a method to accept several types of list without losing type safety, a wildcard is used:

static void afficherNoms(List<? extends CanardEnPlastique> bassin) {
    for (CanardEnPlastique c : bassin) System.out.println(c.getNom());
}

? extends T accepts a list of T or of any subclass of T: Ts can be read from it. ? super T accepts a list of T or of one of its parent classes: Ts can be written to it. To remember this, the usual phrase is “producer extends, consumer super”.

What generics cannot do

At run time, the type parameter no longer exists: this is type erasure, a choice made in 2004 to stay compatible with existing code.

new T()                        // impossible
new T[10]                      // impossible
x instanceof List<String>      // impossible

For the same reason, two methods that differ only by List<String> and List<Integer> have the same signature after erasure, and the compiler rejects them.

Quiz questions

1. Why is an enumeration better than a set of integer constants?

Because an enumeration is a type: no value outside the list is accepted, the values are explicitly grouped, and it can have fields and methods.

2. Is List<String> a subtype of List<Object>?

No, because generics are invariant. If it were, any Object could be added to a list of String, and reading an element would then fail.

3. Why is new T() impossible in a generic class?

Because the type parameter is erased at compile time: at run time, no information about T remains, so there is no class to instantiate.