Wednesday, March 12, 2008

Java 1.5 Explained - Enum

Before Java 1.5, you'd probably write code like the following to implement enumerated types, similar to how it would be done in C using #define:
public final static int SUIT_CLUBS = 1;
public final static int SUIT_DIAMONDS = 2;
public final static int SUIT_HEARTS = 3;
public final static int SUIT_SPADES = 4;

Sure this is a little better than #define, but not much better. Java 1.5 enumerated types are similar to what's available in C++. Here's a C++ example:

enum Suit {
CLUBS = 1,
DIAMONDS = 2,
HEARTS = 3,
SPADES = 4
};
In Java this would be:
enum Suit {
CLUBS(1),
DIAMONDS(2),
HEARTS(3),
SPADES(4)
};
There's a subtle different between the Java and C++ syntax. Java is using parens to initialize the individual enumerated type entries to a specific value. What's really going on here? Let's take a look a some byte code.

First, here is a complete sample of some Java code using enum:
public class Enum
{
public enum Direction { NORTH, SOUTH, EAST, WEST };

public static void main(String[] args)
{
Direction d = Direction.EAST;
System.out.println(d);
}
}
And the corresponding byte code:
public class Enum extends java.lang.Object{
public Enum();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."":()V
4: return

public static void main(java.lang.String[]);
Code:
0: getstatic #2; //Field Enum$Direction.EAST:LEnum$Direction;
3: astore_1
4: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
7: aload_1
8: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
11: return
}
Look at line 0 in the main method. The Direction enum is really a Java class (in this case an inner class within this Enum class example) and it has a static field named EAST. Looking a little closer you can see that the field EAST is also Direction object. That explains the reason for having the parens to initialize the enumerated value. It's actually passing the value to a constructor. Now here's the bytecode for the Direction class that was automatically created:

public final class Enum$Direction extends java.lang.Enum{
public static final Enum$Direction NORTH;

public static final Enum$Direction SOUTH;

public static final Enum$Direction EAST;

public static final Enum$Direction WEST;

public static final Enum$Direction[] values();
Code:
0: getstatic #1; //Field $VALUES:[LEnum$Direction;
3: invokevirtual #2; //Method "[LEnum$Direction;".clone:()Ljava/lang/Object;
6: checkcast #3; //class "[LEnum$Direction;"
9: areturn

public static Enum$Direction valueOf(java.lang.String);
Code:
0: ldc_w #4; //class Enum$Direction
3: aload_0
4: invokestatic #5; //Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
7: checkcast #4; //class Enum$Direction
10: areturn

static {};
Code:
0: new #4; //class Enum$Direction
3: dup
4: ldc #7; //String NORTH
6: iconst_0
7: invokespecial #8; //Method "":(Ljava/lang/String;I)V
10: putstatic #9; //Field NORTH:LEnum$Direction;
13: new #4; //class Enum$Direction
16: dup
17: ldc #10; //String SOUTH
19: iconst_1
20: invokespecial #8; //Method "":(Ljava/lang/String;I)V
23: putstatic #11; //Field SOUTH:LEnum$Direction;
26: new #4; //class Enum$Direction
29: dup
30: ldc #12; //String EAST
32: iconst_2
33: invokespecial #8; //Method "":(Ljava/lang/String;I)V
36: putstatic #13; //Field EAST:LEnum$Direction;
39: new #4; //class Enum$Direction
42: dup
43: ldc #14; //String WEST
45: iconst_3
46: invokespecial #8; //Method "":(Ljava/lang/String;I)V
49: putstatic #15; //Field WEST:LEnum$Direction;
52: iconst_4
53: anewarray #4; //class Enum$Direction
56: dup
57: iconst_0
58: getstatic #9; //Field NORTH:LEnum$Direction;
61: aastore
62: dup
63: iconst_1
64: getstatic #11; //Field SOUTH:LEnum$Direction;
67: aastore
68: dup
69: iconst_2
70: getstatic #13; //Field EAST:LEnum$Direction;
73: aastore
74: dup
75: iconst_3
76: getstatic #15; //Field WEST:LEnum$Direction;
79: aastore
80: putstatic #1; //Field $VALUES:[LEnum$Direction;
83: return
}
There's a lot going on in Direction. It's really just a bunch of code which could have been written by hand prior to Java 1.5, but now the compiler is doing all the work for us. The code is basically doing this:
public class Direction extends java.lang.Enum
{
public final static Direction NORTH;
public final static Direction SOUTH;
public final static Direction EAST;
public final static Direction WEST;

static
{
NORTH = new Direction("NORTH", 0);
SOUTH = new Direction("SOUTH", 1);
EAST = new Direction("EAST", 2);
WEST = new Direction("WEST", 3);

VALUES = new Direction[4];
VALUES[0] = NORTH;
VALUES[1] = SOUTH;
VALUES[2] = EAST;
VALUES[3] = WEST;
}
}
How about that? All that code came from pretty much one line of code in Java 1.5. Notice also that Direction extends a new class in Java 1.5, java.lang.Enum. This new class is where the String and oridinal values for the enumerated type are stored. Now you can see where the nice toString() comes from with enumerated types. In our example above, the System.out.println(d) will print "EAST". The Enum.ordinal() method can be used to get the ordinal value as well.

There's a lot more you can do with enum, like add your own methods that will be included in the class that's automatically generated. Please see Sun's Enum page for more information.