Enum’s named constants;
No 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 typeT
can appear (almost) anywhere in the class body, without anyone caring what type it may be. - Any use of
Foo
needs 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
Foo
must 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 toFoo
is themselves. Foo
has a method that returnsSubClassOfFoo
. Combined with the above idiom, this allowsFoo
to 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>>
.
E
is 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?!