4.2. Enumerated Types
In 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 Basics
An 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 names like
DownloadStatus.CONNECTING,
DownloadStatus.READING, and so on. A variable of
type DownloadStatus can be assigned one of these
four values or null but nothing else. The values
of an enumerated type are called enumerated
values
and are sometimes also referred to as enum
constants
.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 classes
Prior 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;The use of integer constants has a number of shortcomings, the most
public static final int READING = 2;
public static final int DONE = 3;
public static final int ERROR = 4;
important of which is its lack of type safety. If a method expects a
download status constant value, for example, no error checking
prevents me from passing an illegal value. The compiler
can't tell me that I've used the
constant UploadStatus.DONE when I should have used
DownloadStatus.DONE.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 illustrate
common enumerated type programming idioms and demonstrate that
anything you can do with integer constants can be done elegantly,
efficiently, and more safely with enums. First,
however, we consider the basic features of all
enumerated types.
4.2.1.2 Features of enumerated types
The 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:
- Enumerated types have no public
constructor. The only instances of an
enumerated type are those declared by the enum. - Enums are not Cloneable, so copies of the existing
instances cannot be created. - Enums implement java.io.Serializable so they can
be serialized, but the Java serialization mechanism handles them
specially to ensure that no new instances are ever created. - Instances of an enumerated type are
immutable: each enum value retains its identity.
(We'll see later in this chapter that you can add
your own fields and methods to an enumerated type, which means that
you can create enumerated values that have mutable portions. This is
not recommended, but does not affect the basic identity of each
value.) - Instances of an enumerated type are stored in public static
final fields of the type itself. Because these fields are
final, they cannot be overwritten with
inappropriate values: you can't assign the
DownloadStatus.ERROR value to the
DownloadStatus.DONE field, for example. - By convention, the values of enumerated types are written using all
capital letters, just as other static final fields
are. - Because there is a strictly limited set of distinct enumerated
values, it is always safe to compare enum values using the =
= operator instead of calling the
equals()
method. - Enumerated types do have a working equals( )
method, however. The method uses = = internally
and is final so that it cannot be overridden. This
working equals( ) method allows enumerated values
to be used as members of collections such as Set,
List, and Map. - Enumerated types have a working
hashCode() method
consistent with their equals( ) method. Like
equals(), hashCode( ) is
final. It allows enumerated values to be used with
classes like java.util.HashMap. - Enumerated types implement
java.lang.Comparable, and the
compareTo() method
orders enumerated values in the order in which they appear in the
enum declaration. - Enumerated types include a working toString(
) method
that returns the name of the enumerated value. For example,
DownloadStatus.DONE.toString( ) returns the string
"DONE" by default. This method is not
final, and enum types can provide a custom
implementation if they choose. - Enumerated types provide a static valueOf(
) method
that does the opposite of the default toString( )
method. For example,
DownloadStatus.valueOf("DONE") would return
DownloadStatus.DONE. - Enumerated types define a final instance method
named ordinal() that returns an integer for each enumerated
value. The ordinal of an enumerated value represents its position
(starting at zero) in the list of value names in the
enum declaration. You do not typically need to use
the ordinal( ) method, but it is used by a number
of enum-related facilities, as described later in the chapter. - Each enumerated type defines a static method named values(
) that returns an array of enumerated values of that type.
This array contains the complete set of values, in the order they
were declared, and is useful for iterating through the complete set
of possible values. Because arrays are mutable, the values(
) method always returns a newly created and initialized
array. - Enumerated types are subclasses of java.lang.Enum,
which is new in Java 5.0.
(Enum is not itself an enumerated type.) You
cannot produce an enumerated type by manually extending the
Enum class, and it is a compilation error to
attempt this. The only way to define an enumerated type is with the
enum keyword. - It is not possible to extend an enumerated type. Enumerated
types are effectively final, but the
final keyword is neither required nor permitted in
their declarations. Because enums are effectively
final, they may not be
abstract. (We'll return to this
point later in the chapter.) - Like classes, enumerated types may implement
interfaces.
(We'll see how enumerated types may define methods
later in the chapter.)
4.2.2. Using Enumerated Types
The 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 ideally suited
for use with the switch statement, and this
statement has been extended in Java 5.0 to support the use of
enumerated types. If the compile-time type of the
switch expression is an enumerated type, the
case labels must all be unqualified names of
instances of that type. The following hypothetical code shows a
switch statement used with the
DownloadStatus enumerated type.
DownloadStatus status = imageLoader.getStatus();Note that the case labels are just the constant name:
switch(status) {
case CONNECTING:
imageLoader.waitForConnection();
imageLoader.startReading();
break;
case READING:
break;
case DONE:
return imageLoader.getImage();
case ERROR:
throw new IOException(imageLoader.getError());
}
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 convenient since it would
otherwise appear in every single case. However the
requirement that the class name be omitted is
surprising since (in the absence of an import
static declaration) the class name is
required in every other context.If the switch expression
(status in the code above) evaluates to
null, a
NullPointerException is thrown. It is not legal to
use null as the value of a case
label.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:
[5] At the time
of this writing, this warning is expected to appear in Java
5.1.
default: throw new AssertionError("Unexpected enumerated value: " + status);
4.2.2.2 EnumMap
A
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[] {In the big picture, this technique creates a mapping from enumerated
"Connecting...", // CONNECTING
"Loading...", // READING
"Done.", // DONE
"Download Failed." // ERROR
};
int status = getStatus();
String message = statusLineMessages[status];
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 =Like other collection classes in Java 5.0, EnumMap
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);
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 EnumSet
Another 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 ouncesThese power-of-two constants can be combined with the bitwise OR
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
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 correspond to the
ordinal() of
each enumerated value.The espresso drink code above could be rewritten as follows using an
enum and EnumSet:
public enum DrinkFlags {Note that the code above can be made as compact as the integer-based
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);
code with a simple static import:
// Import all static DrinkFlag enum constantsSee Section 2.10 in Chapter 2 for
import static com.davidflanagan.coffee.DrinkFlags.*;
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 numbers of arguments. A varargs (see Chapter 2) form that can accept any number of
arguments is also defined. Here are some other ways that you can use
of() and related EnumSet
factories:
// Make the following examples fit on the page betterThe example code shown here demonstrates the use and capabilities of
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 EnumSet class. Note, however, that an
EnumSet<DrinkFlags> is not really an
appropriate representation for the description of an espresso drink.
An EnumSet<DrinkFlags> might be
overspecified, including both SHORT and
GRANDE, for example, or it might be underspecified
and include no drink size at all.At the root, the problem is that the DrinkFlag
type is a naive translation of the integer bit flags we began this
section with. A better and more complete representation is captured
by the following interface, which requires one value from each of
five different enumerated types and a set of values from a sixth
enum. The enums are defined as nested types
within the interface itself (see Chapter 3).
This example highlights the type safety provided by enumerated types.
It is not possible (as it would be with integer constants) to specify
a drink strength where a drink size is required, for
example.
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 Syntax
The 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:
- You can define your own
fields, methods, and constructors for
the enumerated type. - If you define one or more constructors, you can invoke a constructor
for each enumerated value by following the value name with
constructor arguments in parentheses. - Although an enum may not extend
anything, it may implement one or more interfaces. - Most esoterically, individual enumerated values can have their own
class bodies that override methods defined by the type.
Rather than formally specifying the syntax for each of these advanced
enum declarations, we'll
demonstrate the syntax in the examples that follow.
4.2.3.1 The class body of an enumerated type
Consider
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 {Note that enum syntax requires a semicolon after
// 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; }
}
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 perfectly legal, however, for an enumerated type to
implement one or more interfaces. Suppose, for
example, that you defined a new enumerated type
Unit with an abbrev( ) method
like Prefix has. In this case, you might define an
interface Abbrevable for any objects that have
abbreviations. Your code might look like this:
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 sort. The Operator.ADD
constant might have a compute() method that
behaves differently than the Operator.SUBTRACT
constant, for example.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 legitimate, of
course, to define helper methods or fields that you invoke or use
from the overriding methods.)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 type-specific class body
abstract and to define a value-specific
implementation of the method for every enumerated value. If the
type-specific method is abstract, the compiler
forces you to implement that method for every enumerated value in the
type: it is not possible to accidentally omit an implementation. Note
that even though the type-specific class body contains an
abstract method, the enumerated type as a whole is
not abstract (and may not be declared
abstract) since each value-specific class body
implements the method.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 easiest to
understand, however, if you skip past the values and read the
type-specific class body first:
// 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 experienced programmers. Before you decide to use
this feature, be sure that it is necessary.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
implementations or requires more than a few methods for each value,
you may find it unwieldy to code everything within a single type.
Instead, consider defining your own custom type hierarchy using
traditional class and interface
declarations and whatever singleton instances are necessary.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 {A shortcoming to the switch approach is that each
// 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));
}
}
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 types
Java 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
themselves). The reason for this is that static initialization of
enumerated types (and of all types) proceeds from top to bottom. The
enumerated values are static fields that appear at the top of the
type and are initialized first. Since they are self-typed fields,
they invoke the constructor and any other instance initializer code
of the type. This means that the instance initialization code is
invoked before the static initialization of the class is complete.
Since the static fields have not been initialized yet, the compiler
does not allow them to be used. The only exception is static fields
whose values are compile-time constant expressions (such as integers
and strings) that the compiler resolves.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 inserts
hidden name and ordinal
arguments into any constructor you define. If you define more than
one constructor for the type, it is okay to use
this() to invoke one constructor from the other.
Remember that the class bodies of individual enumerated values (if
you define any) are anonymous, which means that they cannot have any
constructors at all.
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 nuances here.
[6] Josh was cochair of
the the JSR 201 committee that developed many of the new language
features of Java 5.0. He is the creator of and the driving force
behind enumerated types.
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; }
}