Java version 5.0 introduces many dramatic new language
features. This article discusses the new enum facility.
A frequent pattern in Java is the use of enumerated constants-- that is, a set of static final variables that can be semantically grouped. For example, a student grading system needs to be able to capture each of five possible letter grades--A, B, C, D, or F. One standard idiom for implementing this list of constants in Java:
public class Student {
public static final char GRADE_A = 'A';
public static final char GRADE_B = 'B';
public static final char GRADE_C = 'C';
public static final char GRADE_D = 'D';
public static final char GRADE_F = 'F';
// ...
public void addGrade(char grade) {
// ...
}
}
Client code:
new Student().addGrade(Student.GRADE_B);
There are a few problems. First, the declaration code is redundant--public
static final this, public static final that, etc. Secondly,
multiple classes may have use for the constant, but not for the
class in which they are defined. Third, and most problematic, is that
the constants are not type-safe: Nothing prohibits a client from
passing in the character 'Z' to the addGrade method.
The second problem, where the constants are defined, is often solved by use of the Constant Interface pattern. A Constant Interface takes advantage of the fact that you can declare static constants in a Java interface. Classes that need to use the constants simply implement the interface.
However, Joshua Bloch, in his book Effective Java, describes Constant Interface as an anti-pattern. It violates good coding standards by exposing an implementation detail--where constants are defined--to client code. A downside of doing so is that you cannot change the constant interface without impacting the client.
As an alternative, Bloch and I (see Essential Java Style) have promoted the Typesafe Enum pattern. Using Typesafe Enum, you create a new class whose constructor is private. You create static constants of the class to represent the only valid instances. This restricts clients to only use the valid instances.
Building a Typesafe Enum class is not a huge task, but nor is it trivial. Getting nuances such as serialization and synchronization capability working right can be difficult. Thus Bloch was the main promoter of building a correct implementation of Typesafe Enum directly into the Java language.
J2SE 5.0 allows you
to declare a type as an enum type. In its simplest form, an
enum type requires you to specify a type name and the names
of its valid instances. The declaration is about as succinct as possible:
enum Grade { A, B, C, D, F };
That's it. An enum can be a top-level type, or it can be declared
within another class. You use the enum instances just as you would
refer to a static variable:
Grade grade = Grade.C;
The addGrade method declaration changes to:
public void addGrade(Grade grade)
It is no longer possible to pass an invalid parameter! The only valid Grade
instances are defined by the Grade enum itself. (It is also not possible to
subclass the Grade enum, one technique which would have provided
a workaround to the safety mechanism.)
The switch statement has been enhanced to support
enum types:
switch (grade) {
case A: print("great"); break;
case B: print("good"); break;
case C: print("ok"); break;
case D: print("eh"); break;
case F: print("loser"); break;
}
However, as per good OO programming guidelines, you should avoid
the use of switch statements. One way would be to provide a mechanism
in the Grade type itself to return an appropriate message.
An enum type can have fields, constructors, and methods, just like
a class type. Here's how you could associate a message with each
Grade instance:
enum Grade {
A("great"),
B("good"),
C("ok"),
D("eh"),
F("loser");
private String message;
Grade(String message) {
this.message = message;
}
String getMessage() {
return message;
}
}
An example of printing the message:
Grade grade = Grade.C; System.out.println(grade.getMessage());
There are three useful methods that you can use with enum types. First,
the static method values() returns a list of all the enum constants.
The following code uses the J2SE 5.0 for-each loop to iterate the list of
Grade instances and print each:
for (Grade grade: Grade.values()) System.out.println(grade);
You might also find the enum public instance
methods ordinal() and name() useful.
The name() method returns the name of the instance as
specified in the enum declaration. For example, the
name for Grade.C is "C". The ordinal() method returns
an int representing the position of the Grade instance
in the enum list. Thus the ordinal of Grade.A
is 0, the ordinal of Grade.B is 1, and so on. Sun
recommends you not depend on the value of the ordinal.
Since each enum has an associated name, the Enum class provides
a way for you to get the appropriate enum instance given
a String.
Grade grade = Grade.valueOf("F");
You'll get an IllegalArgumentException if you pass a String that
doesn't match any of the enum names.
The follow-up article, Polymorphic Enums in J2SE 5.0 (Tiger), discusses how you can use polymorphism to create distinct behavior for any enum constant.