Groking Enum (aka Enum<E extends Enum<E>>)
# 2004-02-09 11:34:06 -0500 | Java | 14 CommentsNo more tricky integers.
Tiger pounces here.
So, yay! The
tiger
beta has been released, and implements JSR 201
(currently in public review).
201 includes the new enum language facility, so I thought I would have a play
around. The results follow, for those who dare to tread — the tiger pounces here.
Diving in
Here is an example of some code defining and using enums (adapted from the JSR 201 documentation).
public class Card
{
public enum Suit {HEART, DIAMOND, CLUB, SPADE};
public enum Rank {ACE, TWO, THREE, FOUR, FIVE, SIX, SEVEN,
EIGHT, NINE, TEN, JACK, QUEEN, KING};
private final Suit suit;
private final Rank rank;
public Card(Suit suit, Rank rank) {
this.suit = suit;
this.rank = rank;
}
public Suit getSuit() {
return suit;
}
public Rank getRank() {
return rank;
}
public String toString() {
return rank + " of " + suit;
}
public static void main(String... args) {
Card upMySleeve = new Card(Suit.HEART, Rank.ACE);
System.out.println("what is up my sleeve? " + upMySleeve);
// outputs: what is up my sleeve? ACE of HEART
}
}
In some languages, an enum is just a glorified int. But above,
Suit is a fully fledged class. In-fact, it is a subclass of
java.lang.Enum.
The compiler sort of translates the enum Suit into the following class.
That is, each enum constant in Suit is
an instance of Suit, and is declared as a public static final in Suit.
// This is sort of what happens.
public class Suit extends Enum {
public static final Suit HEART = new Suit("HEART", 1);
public static final Suit DIAMOND = new Suit("DIAMOND", 2);
public static final Suit CLUB = new Suit("CLUB", 3);
public static final Suit SPADE = new Suit("SPADE", 4);
public Suit(String name, int ordinal) { super(name, ordinal);}
// some more stuff here
}
But that is not quite the whole picture, because if you go and have a look at
java.lang.Enum
again, you will see that Enum is actually declared as a generic class:
Enum<E extends Enum<E>>.
My first reaction was “WTF does that mean?”. The answer to that question appears below. But first a few preliminaries.
What does Foo<T> really mean?
Very soon, the neurons in your brain are going to form an association
between the Java fragment List<String>
and the concept “a List of Strings”. But if you see Foo<String>,
what does that mean?
Considering the following code:
- T is a type parameter (A). It means that
the class body for Foo is parameterised over some
type (any type!)
T. The typeTcan appear (almost) anywhere in the class body, without anyone caring what type it may be. - Any use of
Fooneeds to supply a type argument for the type parameter ofFoo(B) (C). - It is wholly dependent on the class writer to define the
semantics of type parameters. The Collections API uses type
parameters to say “this is a collection of
T“; but these are semantics defined only by the javadoc. Some generic classFoo<T>need not be a “container ofT“. - The main up-shot here is: type parameters allow users of a class to avoid casting when calling methods on that class.
public class Foo<T> { // (A)
//...
}
Foo<String> f1 = ...; // (B)
Foo<Integer> f2 = ...; // (C)
Then what does does java.lang.Class<T> mean?
If you have had a look at the JDK 1.5 javadoc, then you may have noticed
that java.lang.Class is now declared with a type parameter:
java.lang.Class<T>.
Well, Class is not part of the Collections API, so what neuron-association should we make here?
The trick is to note the change made to the magic .class static attribute: the expression
Foo.class returns an object of type Class<Foo> (instead of just
Class).
The type parameter to Class actually tells the user what class the Class
object represents,
and allows the user to avoid casting when using certain methods on Class.
For example, take
newInstance()
and
cast().
Both methods return an object that is already of type T.
Class<Date> c1 = Date.class; Class c2 = Date.class; Date d1 = c1.newInstance(); // new style, no potential ClassCastException Date d2 = (Date) c2.newInstance(); // old style Object o = d1; // no need to do a manual cast, the cast() method // throws the ClassCastException if necessary Date d3 = c1.cast(o); // old style, need to do a manual cast Date d4 = (Date) o;
So get those neurons ready: Class<T> simply means “the Class instance
for the class T“.
More tricks with type parameters
Take a look at this code.
abstract class Foo<SubClassOfFoo extends Foo<SubClassOfFoo>>
{
/** subclasses are forced to return themselves from this method */
public abstract SubClassOfFoo subclassAwareDeepCopy();
}
class Bar extends Foo<Bar> {
public Bar subclassAwareDeepCopy() {
Bar b = new Bar();
// ...
return b;
}
}
Bar b = new Bar();
Foo<Bar> f = b;
Bar b2 = b.subclassAwareDeepCopy();
Bar b3 = f.subclassAwareDeepCopy(); // no need to cast, return type is Bar
The trick going on with Foo<SubClassOfFoo extends Foo<SubClassOfFoo>>
is:
- Any subclass of
Foomust supply a type argument toFoo. - That type argument must actually be a subclass of
Foo. - Subclasses of
Foo(likeBar) follow the idiom that the type argument they supply toFoois themselves. -
Foohas a method that returnsSubClassOfFoo. Combined with the above idiom, this allowsFooto formulate a contract that says “any subclass of me must implementsubclassAwareDeepCopy()and they must declare that it returns that actual subclass“.
To say that another way: this idiom allows a superclass (such as an Abstract Factory) to define methods whose argument types and return types are in terms of the subclass type, not the superclass type.
And now back to Enum<E extends Enum<E>>
If you grok the idiom being used by Foo and its subclasses above, then
you should be able to see why java.lang.Enum
is declared as
Enum<E extends Enum<E>>.
-
Eis used in the return type of getDeclaringClass(), - and as an argument to compareTo().
Which means you can write code like the following that a) doesn’t need to cast and b) can use methods defined in
Enum in terms of the concrete enum subclass.
Rank r = Rank.ACE; Suit s = Suit.HEART; r.compareTo(s); // syntax error, argument must be of type Rank Rank z = Enum.valueOf(Rank.class, "TWO");
More fun with enum
Methods, fields, constructors
In case you want to have an enum that goes above and beyond
a glorified int,
you can add fields, methods, and constructors to your enum as if it
were a class.
public enum TrafficLightState
{
RED(30), // constructor call
AMBER(10),
GREEN(40); // note the semi-colon
// field
private final int duration;
// constructor
public TrafficLightState(int duration) {
this.duration = duration;
}
// method
public int getDuration() {
return duration;
}
}
Constants can have class bodies
Each constant in an enum can have its own class body (like an anonymous
inner class). This class body creates a subclass of the enum type:
public abstract enum TrafficLightState // note the abstract
{
RED(30) {
public TrafficLightState next() {
return GREEN;
}
},
AMBER(10) { public TrafficLightState next() { return RED; } },
GREEN(40) { public TrafficLightState next() { return AMBER; } };
private final int duration;
public TrafficLightState(int duration) {
this.duration = duration;
}
public int getDuration() {
return duration;
}
public abstract TrafficLightState next();
}
static import and enum constants
You can static-import enum constants, if that floats your boat.
import static foo.TrafficLightState.*; RED.toString(); TrafficLightState state = RED;
What the compiler produces, and the enum’s magic constructor
So, when the compiler sees the first TrafficLightState declaration above,
it transforms it into something like this Java class. Note that the compiler will:
- Add two static utility methods.
- Add the “name” and “ordinal” arguments automatically to every constructor declaration and constructor call.
public class TrafficLightState extends Enum<TrafficLightState>
{
public static final TrafficLightState RED = new TrafficLightState("RED", 1, 30);
public static final TrafficLightState AMBER = new TrafficLightState("AMBER", 2, 10);
public static final TrafficLightState GREEN = new TrafficLightState("GREEN", 3, 40);
private final int duration;
public TrafficLightState(String name, int ordinal, int duration) {
super(name, ordinal);
this.duration = duration;
}
public int getDuration() {
return duration;
}
public static TrafficLightState[] values() {...};
public static TrafficLightState valueOf(String name) {...};
}
So, apart from some extra JDK classes, the JVM has not been modified to add enum
support to JDK 1.5. Syntactic sugar, anyone?
Switch statement support… lacking?
If you use an enum as the argument to a switch statement,
then you don’t need to prefix your constants with the enum class name:
TrafficLightState s = ...;
switch (s) {
case RED:
System.out.println("break");
break;
case GREEN:
System.out.println("go");
break;
case AMBER:
System.out.println("go faster");
break;
}
The one thing I expected to see and didn’t find is the “all cases not covered in switch” compiler warning.
For example, if you remove the RED case statement above, I expected the compile to complain and say
“switch statement over TrafficLightState does not have a RED case”. GCC does this when compiling C++
with the -Wall option.
Serialization and cloning
An enum implements the appropriate Serialization mechanisms to ensure that
only once instance of an enum constant exits in the JVM. You can always safely compare
enum constants using ==.
java.lang.Enum over-rides the clone() method as final, and
always throws CloneNotSupportedException when it is called. Enum constants
are singletons, m-okay?
EnumMap and EnumSet
There are two new collection classes EnumSet and EnumMap.
An EnumSet is a set over the constants of a particular enum.
Since an enum only has a fixed number of constants (say 3), an EnumSet
for a particular enum can only have a maximum of (say) 3 elements.
And exploiting the fact that the constants in an enum have incrementing
ordinal values, EnumSet is implemented efficiently as a bit-vector.
Similarly, an EnumMap maps constants from an enum to
objects (of some type V), and is implemented efficiently as an array.
In closing…
So aren’t enum’s fun?!
Amm.. well, if the compiler generates (at least one) public constructor for an enum class, how can the JVM ensure that the enum instances are singletons?
Yeah, are you sure that constructor is public?
Also, maybe the new -Xlint option for javac catches the "not all values covered in switch" problem.
Yeah, oops, the constructor is not public. (But it may be package scope? I think the compiler might knowingly forbid you from calling the constructor of an enum, and maybe Class.newInstance() fails on enums as well? Not sure.).
And no, -Xlint does not warn about the "not all values covered in switch" problem.
Top posting Matt - I learnt a lot reading this. Once again you have proven that you a "language lawyer". :-)
All your enums are belong to us.
The trick with Foo<SubClassOfFoo extends Foo<SubClassOfFoo>> is really nice, but one thing is still unclear to me: How can you ensure that the trick works not only for direct subclasses of Foo (like Bar) but also for indirect subclasses. I tried to manage it, but I found no solution. Implementing that feature for a whole class tree would be a powerful tool.
Daniel, I can’t come up with an answer to your question… my brain just doesn’t want to stretch that far at the moment :S
Matt, I enjoyed your article. I read it in search of an answer to this question: how to I get a java enum into and out of a value that I can easily save to a database? In C/C++ or the old handrolled java way, saving to and restoring from int was easy. What have I missed?
Joe, In java, enums can be "externalized" easily as strings as opposed to ints. Consider this example:
public class EnumTest {enum E {
red, green, blue;
}
}
If you really want to treat externalize as ints, maybe do something like this:
public class EnumTest2 {enum E {
red(1), green(2), blue(3);
public final int i;
}
Joe, oops… Enum already contains an int "ordinal", so you can just do this (and you could build a static hashmap to make the valueOf() faster):
public class EnumTest2 {enum E {
red, green, blue;
public static E valueOf(int i) {
for(E e : values()) {
if (e.ordinal() == i) {
return e;
}
}
throw new IllegalArgumentException("wtf is " + i);
}
}
How would I go about if I want to map each enum to a String value differing from it’s name?
I tried the following, but the compiler says "valueOf(java.lang.String) is already defined in se.exder.logic.types.SpecialService"
enum SpecialService {
CUSTOMS("ABW"),
ALLOWANCE("ACA"),
FEE("ACN"),
FREIGHT("FC"),
QUANTITYDISCOUNT("QD"),
PENALTY("SC"),
SPECIALHANDLING("SH");
private String code;
private SpecialService(String c) {
this.code = c;
}
public String toString() {
return code;
}
public static SpecialService valueOf(String name) {
for(SpecialService ss : values()) {
if(ss.code.equals(name))
return ss;
}
}
}
This doesn’t work either ("call to super not allowed in enum constructor").
enum SpecialService {
CUSTOMS("ABW",1),
ALLOWANCE("ACA",2),
FEE("ACN",3),
FREIGHT("FC",4),
QUANTITYDISCOUNT("QD",5),
PENALTY("SC",6),
SPECIALHANDLING("SH",7);
private SpecialService(String name, int ordinal) {
super(name, ordinal);
}
}
I find something strange that i cannot explain :
this constructor
cannot be used like this
EnumMap> delta;
DFA automata;
[...]
automata = new DFA(delta);
I need to create this constructor :
So I conclude that EnumMap> do not inherit from Map>.
I’ve tried
public DFA (EnumMap>> delta)
but it didn’t work either, because ? is not allow in constructor.
This is not more than annoying… but now I’d like to write something like
and nor
neither
works as body -_-
The first tells me that there is not constructor in EnumMap that matchs.
The second tells me that there is incompatible types.
Do you have an idea ?
(more a reminder to self…)
>To say that another way: this idiom allows a superclass
>(such as an Abstract Factory) to define methods whose
>argument types and return types are in terms of the
>subclass type, not the superclass type.
More generically speaking: The idiom makes a available a type parameter to the superclass “containing” the type of the subclass.