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 ofList<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.