4.2. Enumerated TypesIn previous chapters, we've seen the class keyword used to define class types, and the interface keyword used to define interface types. This section introduces the enum keyword, which is used to define an enumerated type (informally called an enum). Enumerated types are new in Java 5.0, and the features described here cannot be used (although they can be partially simulated) prior to that release. We begin with the basics: how to define and use an enumerated type, including common programming idioms involving enumerated types and values. Next, we discuss the more advanced features of enums and show how to simulate enums prior to Java 5.0. 4.2.1. Enumerated Types BasicsAn enumerated type is a reference type with a finite (usually small) set of possible values, each of which is individually listed, or enumerated. Here is a simple enumerated type defined in Java:
public enum DownloadStatus { CONNECTING, READING, DONE, ERROR }
Like
class
and
interface
, the
enum
keyword defines a new reference type. The single line of Java code above defines an enumerated type named
DownloadStatus
. The body of this type is simply a comma-separated list of the four values of the type. These values are like
static final
fields (which is why their names are capitalized), and you refer to them with
It is possible to define more complex enumerated types than the one shown here, and we describe the complete enum syntax later in this chapter. For now, however, you can define simple, but very useful, enumerated types with this basic syntax. 4.2.1.1 Enumerated types are classesPrior to the introduction of enumerated types in Java 5.0, the DownloadStatus values would probably have been implemented as integer constants with lines like the following in a class or interface: public static final int CONNECTING = 1; public static final int READING = 2; public static final int DONE = 3; public static final int ERROR = 4;
The use of integer constants has a number of shortcomings, the most important of which is its lack of type safety. If a method expects a download status constant value, for example, no error checking
Fortunately, enumerated types in Java are not simple integer constants. The type defined by an enum keyword is actually a class and its enumerated values are instances of that class. This provides type safety: if I try to pass a DownloadStatus value to a method that expects an UploadStatus , the compiler issues an error. Enumerated types do not have a public constructor, so a program cannot create a new undefined instance of the type. If a method expects a DownloadStatus , it can be confident that it will not be passed some unknown instance of the type.
If you are accustomed to writing code using integer constants instead of true enumerated types, you have probably already made a list of pragmatic advantages of integers over objects for enumerated values. Hold your judgment, however: the sections that follow
4.2.1.2 Features of enumerated typesThe following list describes the basic facts about enumerated types. These are the features of enums that you need to know to understand and use them effectively:
4.2.2. Using Enumerated TypesThe following sections illustrate common idioms for working with enumerated types. They demonstrate the use of the switch statement with enumerated types and introduce the important new EnumSet and EnumMap collections. 4.2.2.1 Enums and the switch statement
In Java 1.4 and earlier, the
switch
statement works only with
int
,
short
,
char
, and
byte
values. Because enumerated types have a finite set of values, they are
DownloadStatus status = imageLoader.getStatus();
switch(status) {
case CONNECTING:
imageLoader.waitForConnection();
imageLoader.startReading();
break;
case READING:
break;
case DONE:
return imageLoader.getImage();
case ERROR:
throw new IOException(imageLoader.getError());
}
Note that the case labels are just the constant name: the syntax of the
switch
statement does not allow the class name
DownloadStatus
to appear here. The ability to omit the class name is very
If the
switch
expression (
status
in the code above)
If you use the switch statement on an enumerated type and do not include either a default : label or a case label for each enumerated value, the compiler will most likely issue an -Xlint warning letting you know that you have not written code to handle all possible values of the enumerated type. [5] Even when you do write a case for each enumerated value, you may still want to include a default : clause; this covers the possibility that a new value is added to the enumerated type after your switch statement has been compiled. The following default clause, for example, could be added to the switch statement shown earlier:
default: throw new AssertionError("Unexpected enumerated value: " + status);
4.2.2.2 EnumMapA common programming technique when using integer constants instead of true enumerated values is to use those constants as array indexes. For example, if the DownloadStatus values are defined as integers between 0 and 3, we can write code like this:
String[] statusLineMessages = new String[] {
"Connecting...", // CONNECTING
"Loading...", // READING
"Done.", // DONE
"Download Failed." // ERROR
};
int status = getStatus();
String message = statusLineMessages[status];
In the big picture, this technique creates a mapping from enumerated integer constants to strings. We can't use Java's enumerated values as array indexes, but we can use them as keys in a java.util.Map . Because this is a common thing to do, Java 5.0 defines a new java.util.EnumMap class that is optimized for exactly this case. EnumMap requires an enumerated type as its key, and, relying on the fact the number of possible keys is finite, it uses an array to hold the corresponding values. This implementation means that EnumMap is more efficient than HashMap . The EnumMap equivalent of the code above is:
EnumMap<DownloadStatus,String> messages =
new EnumMap<DownloadStatus,String>(DownloadStatus.class);
messages.put(DownloadStatus.CONNECTING, "Connecting...");
messages.put(DownloadStatus.READING, "Loading...");
messages.put(DownloadStatus.DONE, "Done.");
messages.put(DownloadStatus.ERROR, "Download Failed.");
DownloadStatus status = getStatus();
String message = messages.get(status);
Like other collection classes in Java 5.0, EnumMap is a generic type that accepts type parameters. The use of an EnumMap to associate a value with each instance of an enumerated type is appropriate when you're working with an enum defined elsewhere. If you defined the enum value yourself, you can create the necessary associations as part of the enum definition itself. We'll see how to do this later in the chapter. 4.2.2.3 EnumSetAnother common programming idiom when using integer-based constants instead of an enumerated type is to define all the constants as powers of two so that a set of those constants can be compactly represented as bit-flags in an integer. Consider the following flags that describe options that can apply to an American-style espresso drink: public static final int SHORT = 0x01; // 8 ounces public static final int TALL = 0x02; // 12 ounces public static final int GRANDE = 0x04; // 16 ounces public static final int DOUBLE = 0x08; // 2 shots of espresso public static final int SKINNY = 0x10; // made with nonfat milk public static final int WITH_ROOM = 0x20; // leave room for cream public static final int SPLIT_SHOT = 0x40; // half decaffeinated public static final int DECAF = 0x80; // fully decaffeinated These power-of-two constants can be combined with the bitwise OR operator ( ) to create a compact set of constants that is easy to work with: int drinkflags = DOUBLE SHORT WITH_ROOM; The bitwise AND operator ( & ) can be used to test for the presence or absence of bits: boolean isBig = (drinkflags & (TALL GRANDE)) != 0;
If we step back from the binary representation of these bit flags and the boolean operators that manipulate them, we can see that integer bit flags are simply compact sets of values. For reference types such as Java's enumerated values, we can use a
java.util.Set
instead. Since this is an important and common thing to do with enumerated values, Java 5.0 provides the special-purpose
java.util.EnumSet
class. Like
EnumMap
,
EnumSet
is optimized for enumerated types. It requires that its members be values of the same enumerated type and uses a compact and fast representation of the set based on bit flags that
The espresso drink code above could be rewritten as
public enum DrinkFlags {
SHORT, TALL, GRANDE, DOUBLE, SKINNY, WITH_ROOM, SPLIT_SHOT, DECAF
}
EnumSet<DrinkFlags> drinkflags =
EnumSet.of(DrinkFlags.DOUBLE, DrinkFlags.SHORT, DrinkFlags.WITH_ROOM);
boolean isbig =
drinkflags.contains(DrinkFlags.TALL)
drinkflags.contains(DrinkFlags.GRANDE);
Note that the code above can be made as compact as the integer-based code with a simple static import: // Import all static DrinkFlag enum constants import static com.davidflanagan.coffee.DrinkFlags.*; See Section 2.10 in Chapter 2 for details on the import static declaration.
EnumSet
defines a number of useful factory methods for initializing sets of enumerated values. The
of()
method shown above is overloaded: several versions of the method take different fixed
// Make the following examples fit on the page better
import static com.davidflanagan.coffee.DrinkFlags.*;
// We can remove individual members or sets of members from a set.
// Start with a set that includes all enumerated values, then remove a subset:
EnumSet<DrinkFlags> fullCaffeine = EnumSet.allOf(DrinkFlags.class);
fullCaffeine.removeAll(EnumSet.of(DECAF, SPLIT_SHOT));
// Here's another technique to achieve the same result:
EnumSet<DrinkFlags> fullCaffeine =
EnumSet.complementOf(EnumSet.of(DECAF,SPLIT_SHOT));
// Here's an empty set if you ever need one
// Note that since we don't specify a value, we must specify the element type
EnumSet<DrinkFlags> plainDrink = EnumSet.noneOf(DrinkFlags.class);
// You can also easily describe a contiguous subset of values:
EnumSet<DrinkFlags> drinkSizes = EnumSet.range(SHORT, GRANDE);
// EnumSet is Iterable, and its iterator returns values in ordinal() order,
// so it is easy to loop through the elements of an EnumSet.
for(DrinkFlag size : drinkSizes) System.out.println(size);
The example code shown here
At the root, the problem is that the
DrinkFlag
type is a naive translation of the integer bit flags we
public interface Espresso {
enum Drink { LATTE, MOCHA, AMERICANO, CAPPUCCINO, ESPRESSO }
enum Size { SHORT, TALL, GRANDE }
enum Strength { SINGLE, DOUBLE, TRIPLE, QUAD }
enum Milk { SKINNY, ONE_PERCENT, TWO_PERCENT, WHOLE, SOY }
enum Caffeine { REGULAR, SPLIT_SHOT, DECAF }
enum Flags { WITH_ROOM, EXTRA_HOT, DRY }
Drink getDrink();
Size getSize();
Strength getStrength();
Milk getMilk();
Caffeine getCaffeine();
java.util.Set<Flags> getFlags();
}
4.2.3. Advanced Enum SyntaxThe examples shown so far have all used the simplest enum syntax in which the body of the enum simply consists of a comma-separated list of value names. The full enum syntax actually provides quite a bit more power and flexibility:
Rather than
4.2.3.1 The class body of an enumerated typeConsider the type Prefix , defined below. It is an enum that includes a regular class body following the list of enumerated values. It defines two instance fields and accessor methods for those fields. It defines a custom constructor that initializes the instance field. Each named value of the enumerated type is followed by constructor arguments in parentheses:
public enum Prefix {
// These are the values of this enumerated type.
// Each one is followed by constructor arguments in parentheses.
// The values are separated from each other by commas, and the
// list of values is terminated with a semicolon to separate it from
// the class body that follows.
MILLI("m", .001),
CENTI("c", .01),
DECI("d", .1),
DECA("D", 10.0),
HECTA("h", 100.0),
KILO("k", 1000.0); // Note semicolon
// This is the constructor invoked for each value above.
Prefix(String abbrev, double multiplier) {
this.abbrev = abbrev;
this.multiplier = multiplier;
}
// These are the private fields set by the constructor
private String abbrev;
private double multiplier;
// These are accessor methods for the fields. They are instance methods
// of each value of the enumerated type.
public String abbrev() { return abbrev; }
public double multiplier() { return multiplier; }
}
Note that enum syntax requires a semicolon after the last enumerated value if that value is followed by a class body. This semicolon may be omitted in the simple case where there is no class body. It is also worth noting that enum syntax allows a comma following the last enumerated value. A trailing comma looks somewhat odd but prevents syntax errors if in the future you add new enumerated values or rearrange existing ones. 4.2.3.2 Implementing an interface
An
enum
cannot be declared to
extend
a class or enumerated type. It is
public interface Abbrevable {
String abbrev();
}
public enum Prefix implements Abbrevable {
// the body of this enum type remains the same as above.
}
4.2.3.3 Value-specific class bodies
In addition to defining a class body for the enumerated type itself, you can also provide a class body for individual enumerated values within the type. We've seen above that we can add fields to an enumerated type and use a constructor to initialize those fields. This gives us value-specific data. The ability to define class bodies for each enumerated value means that we can write methods for each one: this gives us value-specific
behavior
. Value-specific behavior is useful when defining an enumerated type that represents an operator in an expression parser or an opcode in a virtual machine of some
To define a class body for an individual enumerated value, simply follow the value name and its constructor arguments with the class body in curly braces. Individual values must still be separated from each other with commas, and the last value in the list must be separated from the type's class body with a semicolon: it can be easy to forget about this required punctuation with the presence of curly braces for class and method bodies.
Each value-specific class body you write results in the creation of an anonymous subclass of the enumerated type and makes the enumerated value a singleton instance of that anonymous subclass. (Enumerated types can not be extended, but they are not strictly
final
in the sense that
final
classes are since they can have these anonymous subclasses.) Because these subclasses are anonymous, you cannot refer to them in your code: the compile-time type of each enumerated value is the enumerated type, not the anonymous subclass specific to that value. Therefore, the only useful thing you can do in value-specific class bodies is override methods defined by the type itself. If you define a new public field or method, you will not be able to refer to or invoke it. (It is perfectly
A common pattern is to define default behavior in a method of the type-specific class body. Then, each enumerated value that requires behavior other than the default can override that method in its value-specific class body. A very useful variant of this pattern is to declare the method in the
The following code is an excerpt from a larger example that uses an enumerated type to represent the opcodes of a simulated stack-based CPU. The
Opcode
enumerated type defines an
abstract
method
perform()
, which is then implemented by the class body of each value of the type. The type includes a constructor to illustrate the full syntax for each enumerated value: name, constructor arguments, and class body.
enum
syntax requires the enumerated values and their class bodies to appear first. The code is
// These are the opcodes that our stack machine can execute.
public enum Opcode {
// Push the single operand onto the stack
PUSH(1) {
public void perform(StackMachine machine, int[] operands) {
machine.push(operands[0]);
}
}, // Remember to separate enum values with commas
// Add the top two values on the stack and push the result
ADD(0) {
public void perform(StackMachine machine, int[] operands) {
machine.push(machine.pop() + machine.pop());
}
},
/* Other opcode values have been omitted for brevity */
// Branch if Equal to Zero
BEZ(1) {
public void perform(StackMachine machine, int[] operands) {
if (machine.top() == 0) machine.setPC(operands[0]);
}
}; // Remember the required semicolon before the class body
// This is the constructor for the type.
Opcode(int numOperands) { this.numOperands = numOperands; }
int numOperands; // how many integer operands does it expect?
// Each opcode constant must implement this abstract method in a
// value-specific class body to perform the operation it represents.
public abstract void perform(StackMachine machine, int[] operands);
}
4.2.3.3.1 When to use value-specific class bodies
Value-specific class bodies are an extremely powerful language feature when each enumerated value must perform a unique computation of some sort. Keep in mind, however, that value-specific class bodies are an advanced feature that is not commonly used and may be confusing to less
Before using value-specific class bodies, ensure that your design is neither too simple nor too complex for the feature. First, check that you do indeed require value-specific behavior and not simply value-specific data. Value-specific data can be encoded in constructor arguments as was shown in the Prefix example earlier. It would be unnecessary and inappropriate to rewrite that example to use value-specific versions of the abbrev( ) method, for example.
Next, think about whether an enumerated type is sufficient for your needs. If your design requires value-specific methods with complex
If value-specific behavior is indeed required within the framework of an enumerated type, value-specific class bodies are appropriate. Whether value-specific bodies are truly elegant or simply confusing is a matter of opinion, and some programmers prefer to avoid them when possible. An alternative that appeals to some is to encode the value-specific behavior in a type-specific method that uses a switch statement to treat each value as a separate case . The compute( ) method of the following enum is an example. The simplicity of this enumerated type makes a switch statement a compelling alternative to value-specific class bodies:
public enum ArithmeticOperator {
// The enumerated values
ADD, SUBTRACT, MULTIPLY, DIVIDE;
// Value-specific behavior using a switch statement
public double compute(double x, double y) {
switch(this) {
case ADD: return x + y;
case SUBTRACT: return x - y;
case MULTIPLY: return x * y;
case DIVIDE: return x / y;
default: throw new AssertionError(this);
}
}
// Test case for using this enum
public static void main(String args[]) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
for(ArithmeticOperator op : ArithmeticOperator.values())
System.out.printf("%f %s %f = %f%n", x, op, y, op.compute(x,y));
}
}
A shortcoming to the switch approach is that each time you add a new enumerated value, you must remember to add a corresponding case to the switch statement. And if there is more than one method that uses a switch statement, you'll have to maintain their switch statements in parallel. Forgetting to implement value-specific behavior using a switch statement leads to a runtime AssertionError . With a value-specific class body overriding an abstract method in the type-specific class body, the same omission leads to a compilation error and can be corrected sooner. The performance of value-specific methods and switch statements in a type-specific method are quite similar. The overhead of virtual method invocation in one case is balanced by the overhead of the switch statement in the other. Value-specific class bodies result in the generation of additional class files, each of which has overhead in terms of storage space and loading time. 4.2.3.4 Restrictions on enum typesJava places a few restrictions on the code that can appear in an enumerated type. You won't encounter these restrictions that often in practice, but you should still be aware of them. When you define an enumerated type, the compiler does a lot of work behind the scenes: it creates a class that extends java.lang.Enum and it generates the values() and valueOf() methods as well as the static fields that hold the enumerated values. If you include a class body for the type, you should not include members whose names conflict with the automatically generated members or with the final methods inherited from Enum . enum types may not be declared final . Enumerated types are effectively final, and the compiler does not allow you to extend an enum . The class file generated for an enum is not technically declared final if the enum contains value-specific class bodies, however. Types in Java may not be both final and abstract . Since enumerated types are effectively final , they may not be declared abstract . If the type-specific class body of an enum declaration contains an abstract method, the compiler requires that each enum value have a value-specific class body that includes an implementation of that abstract method. Considered as a self-contained whole, the enumerated type defined this way is not abstract .
The constructor, instance field initializers, and instance initializer blocks of an enumerated type are subject to a sweeping but obscure restriction: they may not use the static fields of the type (including the enumerated values
If you define a constructor for an enumerated type, it may not use the
super( )
keyword to invoke the superclass constructor. This is because the compiler automatically
4.2.4. The Typesafe Enum Pattern
For a deeper understanding of how the
enum
keyword works, or to be able to simulate enumerated types prior to Java 5.0, it is useful to understand the
Typesafe Enum Pattern
. This pattern is described definitively by Joshua Bloch
[6]
in his book
Effective Java Programming Language Guide
(Addison Wesley); we do not cover all the
If you want to use the enumerated type Prefix (from earlier in the chapter) prior to Java 5.0, you could approximate it with a class like the following one. Note, however, that instances of this class won't work with the switch statement or with the EnumSet and EnumMap classes. Also, the code shown here does not include the values() or valueOf( ) methods that the compiler generates automatically for true enum types. A class like this does not have special serialization support like an enum type does, so if you make it Serializable , you must provide a readResolve( ) method to prevent deserialization from creating multiple instances of the enumerated values.
public final class Prefix {
// These are the self-typed constants
public static final Prefix MILLI = new Prefix("m", .001);
public static final Prefix CENTI = new Prefix("c", .01);
public static final Prefix DECI = new Prefix("d", .1);
public static final Prefix DECA = new Prefix("D", 10.0);
public static final Prefix HECTA = new Prefix("h", 100.0);
public static final Prefix KILO = new Prefix("k", 1000.0);
// Keep the fields private so the instances are immutable
private String name;
private double multiplier;
// The constructor is private so no instances can be created except
// for the ones above.
private Prefix(String name, double multiplier) {
this.name = name;
this.multiplier = multiplier;
}
// These accessor methods are public
public String toString() { return name; }
public double getMultiplier() { return multiplier; }
}
|