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.
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.
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; 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
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.
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 arefinal
, they cannot be overwritten with inappropriate values: you can’t assign theDownloadStatus.ERROR
value to theDownloadStatus.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 theequals()
method.Enumerated types do have a working
equals( )
method, however. The method uses= =
internally and isfinal
so that it cannot be overridden. This workingequals( )
method allows enumerated values to be used as members of collections such asSet
,List
, andMap
.Enumerated types have a working
hashCode()
method consistent with theirequals( )
method. Likeequals()
,hashCode( )
isfinal
. It allows enumerated values to be used with classes likejava.util.HashMap
.Enumerated types implement
java.lang.Comparable
, and thecompareTo()
method orders enumerated values in the order in which they appear in theenum
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 notfinal
, and enum types can provide a custom implementation if they choose.Enumerated types provide a static
valueOf( )
method that does the opposite of the defaulttoString( )
method. For example,DownloadStatus.valueOf("DONE")
would returnDownloadStatus.DONE
.Enumerated types define a
final
instance method namedordinal()
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 theenum
declaration. You do not typically need to use theordinal( )
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, thevalues( )
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 theEnum
class, and it is a compilation error to attempt this. The only way to define an enumerated type is with theenum
keyword.It is not possible to extend an enumerated type. Enumerated types are effectively
final
, but thefinal
keyword is neither required nor permitted in their declarations. Because enums are effectivelyfinal
, they may not beabstract
. (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.)
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.
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(); 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 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:
default: throw new AssertionError("Unexpected enumerated value: " + status);
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[] { "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.
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 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 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 { 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 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 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 demonstrates the use and capabilities of
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(); }
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 notextend
anything, it mayimplement
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.
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 { // 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.
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. }
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); }
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 { // 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.
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.
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.
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; } }
Get Java in a Nutshell, 5th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.