Team LiB
Previous Section Next Section

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;
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.

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();
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:

[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[] {
    "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 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 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();
}

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 {
    // 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 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 {
    // 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 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; }
}

    Team LiB
    Previous Section Next Section