madbean

Groking Enum (aka Enum>)

09 Feb 2004

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 type T 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 of Foo (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 class Foo<T> need not be a "container of T".
  • 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 to Foo.
  • That type argument must actually be a subclass of Foo.
  • Subclasses of Foo (like Bar) follow the idiom that the type argument they supply to Foo is themselves.
  • Foo has a method that returns SubClassOfFoo. Combined with the above idiom, this allows Foo to formulate a contract that says "any subclass of me must implement subclassAwareDeepCopy() 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>>.

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?!

  • Home
  • Blog