Chapter 6. Enumeration Types

 

Four be the things I am wiser to know: Idleness, sorrow, a friend, and a foe. Four be the things I'd been better without: Love, curiosity, freckles, and doubt. Three be the things I shall never attain: Envy, content, and sufficient champagne. Three be the things I shall have till I die: Laughter and hope and a sock in the eye.

 
 --Dorothy Parker, “Inventory”

An enumeration type—sometimes known as an enumerated type, or more simply as an enum—is a type for which all values for the type are known when the type is defined. For example, an enum representing the suits in a deck of cards would have the possible values hearts, diamonds, clubs, and spades; and an enum for the days of the week would have the values Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, and Sunday.

In some programming languages, enums are nothing more than a set of named integer values, but in the Java programming language an enum is a special kind of class, with an instance that represents each value of the enum.

A Simple Enum Example

An enum for the suits in a deck of cards can be declared as follows:

enum Suit { CLUBS, DIAMONDS, HEARTS, SPADES }

In this basic form, an enum declaration consists of the keyword enum, an identifier that names the enum, and a body that simply names each of the values, or constants, of the enum.[1] By convention, enum constants always have uppercase names. The enum constants are static fields of the class:

Suit currentSuit = ... ;
if (currentSuit == Suit.DIAMONDS) ... ;

Each enum constant actually refers to an instance of the enum class. You cannot construct an instance of an enum by using new—it is as if the enum had no accessible constructors. You may only use those objects created for the enum constants.

While this simple usage appears little different from declaring a set of named integer constants, it has the significant difference of being completely type-safe: It is impossible to assign anything to a reference of type Suit except one of the four defined Suit enum constants or null. As you will see, enums also provide much more sophisticated usage than can be achieved with named integer constants.

Exercise 6.1Define simple enums for the days of the week and traffic light colors.

Exercise 6.2Redo your solution to Exercise 2.17 to use an enum to represent the TURN_LEFT and TURN_RIGHT constants. What advantage is there to using the enum?

Exercise 6.3Redefine the Verbose interface from Section 4.2.1 on page 121 to use an enum for the verbosity level instead of integer constants.

Enum Declarations

An enum is a special kind of class, but an enum declaration has restrictions on its form and use that are different from those of a normal class. An enum declaration is like a class declaration with two exceptions: The keyword enum is used instead of class, and before declaring any class members (or initialization blocks) an enum must first declare all of its enum constants. If an enum does declare anything other than its enum constants, then the list of enum constants must be termi ated by a semicolon. For example, this variation of Suit provides a method to tell you how many suits there are:

enum Suit {
    CLUBS, DIAMONDS, HEARTS, SPADES;

    public static int getSize() { return 4; }
}

For convenience, you're allowed to have a comma after the last enum constant (but before the semicolon if present), whether or not any other class declarations follow. This is useful for declaring an enum constant per line:

enum Suit {
    CLUBS,
    DIAMONDS,
    HEARTS,
    SPADES,
}

Being able to keep the extraneous comma allows you to reorder lines without having to remember to add the comma to the old last line or remove it from the new last line. (Array initialization lists have the same feature—see page 175.)

An enum cannot be declared to extend another type because all enums implicitly extend java.lang.Enum, which we discuss a little later. Nor can any other type extend an enum (not even another enum), because all enum types act as if they are implicitly final. An enum can declare that it implements one or more interfaces—in fact all enums are implicitly Serializable (see “Object Serialization” on page 549) and Comparable.

The possible members of an enum include all the class members: fields, methods, and nested types, including nested enums—though it should be rare to require such sophistication: The main attraction of enums is their simplicity. The enum constants themselves are implicitly static fields with the same type as the enum.

Every enum type E has two static methods that are automatically generated for it by the compiler:

  • public static E[] values()

    • Returns an array containing each of the enum constants in the order in which they were declared.

  • public static E valueOf(String name)

    • Returns the enum constant with the given name. If the name does not match an enum constant name exactly then an IllegalArgumentException is thrown.

Note that the length of the array returned by values tells you how many enum constants there are in the enum, so the getSize method we demonstrated is not needed in practice.

An enum type is not allowed to override the finalize method from Object. Enum instances may never be finalized (see “Finalization” on page 449).

Enum Modifiers

An enum declaration can be preceded by certain modifiers:

  • annotations—. Annotations and annotation types are discussed in Chapter 15.

  • access modifiers—. Nested enum declarations can have any of the access modifiers, while a top-level enum, as for a top-level class, is either public or in the absence of an access modifier, accessible only within its own package.

  • static—. All nested enums are implicitly static because they contain static members. By convention, the static modifier is always omitted.

  • strict floating point—. All floating-point arithmetic within an enum declared to be strictfp is evaluated strictly. See Section 9.1.3 on page 203 for details.

When multiple modifiers are applied to the same interface declaration, we recommend using the order listed above.

An enum cannot be declared abstract because you cannot extend it to provide missing method implementations—however, an enum can declare abstract methods. You'll shortly see how this apparent contradiction is resolved.

An enum also cannot be explicitly declared final, though it acts as if it were final. You'll shortly see why this is so, too.

Enum Constant Declarations

The simplest enum constant declaration just gives a name for each enum constant, as you have seen. Such enum constants implicitly define a public, static, and final field with the given name and of the same type as the enum. These implicit fields are initialized by constructing an instance of the enum as described below. An enum constant declaration cannot have modifiers applied to it, except for annotations (see Chapter 15).

Construction

An enum constant declaration that consists of only a name causes an object of the enum type to be created with the (implicit or explicit) no-arg constructor. An enum can declare arbitrary constructors, just like any other class. You select which constructor should be used for a particular enum constant by supplying arguments that match the constructor parameter types:

enum Suit {
    CLUBS("CLUBS"),
    DIAMONDS("DIAMONDS"),
    HEARTS("HEARTS"),
    SPADES("SPADES");

    String name;
    Suit(String name) { this.name = name; }

    public String toString() { return name; }
}

Here each Suit value has a name field set by the constructor that takes a String, and in each enum constant declaration we provide the desired name argument. This is such a common thing to want with enum constants, however, that this functionality has been built-in so you don't have to write the name strings yourself—invoking thename method on an enum constant will return its name as a String.

You can get a feel for how an enum is internally defined from the following pseudo-code, which shows a mock class definition for the above Suit enum:

class Suit extends Enum<Suit>    // pseudo-code only
    implements Comparable<Suit>, Serializable {

    public static final Suit CLUBS = new Suit("CLUBS");
    public static final Suit DIAMONDS = new Suit("DIAMONDS");
    public static final Suit HEARTS = new Suit("HEARTS");
    public static final Suit SPADES = new Suit("SPADES");

    String name;

    Suit(String name) { this.name = name; }
    public String toString() { return name; }

    // ... compiler generated methods ...
}

There is a bit more to the detail, but the above gives you the general idea.

The constructor to invoke is determined according to the usual rules for matching argument types with constructor parameter types—see “Finding the Right Method” on page 224.

There are three restrictions on the definition of an enum constructor:

  • All enum constructors are private. While you can use the private access modifier in the constructor declaration, by convention it is omitted. Private constructors ensure that an enum type cannot be instantiated directly.

  • The constructor cannot explicitly invoke a superclass constructor. The chaining to the super constructor is handled automatically by the compiler.

  • An enum constructor cannot use a non-constant static field of the enum.

This last restriction requires a little explanation. Because each enum constant is a static field of the enum type, the constructors are executed during static initialization of the enum class. The enum constant declarations must be the first declarations in the type, so the constructors for these values will always be the first code executed during static initialization. Any other static fields will be initialized afterward. So if a constructor were to refer to a static (non-constant) field of the enum, it would see the default uninitialized value. This would nearly always be an error, and so it is simply disallowed.

The work-around is quite straight-forward: Declare a static initialization block that does what the constructor would like to have done. This block can refer to any enum constant or can iterate through all of them because all will have been constructed before the block can execute.

Exercise 6.4Expand your traffic light color enum from Exercise 6.1 on page 152 so that each enum constant has a suitable Color object that can be retrieved with getColor.

Constant Specific Behavior

Many enums define simple, passive types whose sole purpose is to provide the named enum constants—an enum like Suit is a good example. Occasionally, enum constants will have state associated with them that will be set by a constructor and accessed with a method on the enum. For example, consider an enum that defined the nine planets of our solar system, and that allowed the mass of each planet to be set and queried. In some circumstances, the enum may represent an entity with inherent behavior that varies between the different enum constants—this is known as constant-specific behavior.

Suppose you were writing a computer chess program[2] and you wanted to represent the different kinds of chess pieces, you might use a simple enum like this:

enum ChessPiece {
    PAWN, ROOK, BISHOP, KNIGHT, KING, QUEEN;
}

The rules for manipulating the different chess pieces could be defined in a class ChessRules, which might then have a method that returns the set of reachable positions for a given kind of piece, given its current position:

Set<Position> reachable(ChessPiece type, Position current) {
    if (type == ChessPiece.PAWN)
        return pawnReachable(current);
    else if (type == ChessPiece.ROOK)
        return rookReachable(current);
    // ...
    else if (type == ChessPiece.QUEEN)
        return queenReachable(current);
    else
        throw new Error("Unknown type");
}

The job of reachable is to dispatch actual calculations to methods that know how to handle specific chess pieces, so it has to consider all the possible values of the chess piece type that was passed to it. Whenever you see a chain of ifelse statements, as above, or equivalently a switch statement (which you'll learn about in Chapter 10) that distinguishes different objects or types of object, ask yourself if there is a way to have the object decide what needs to be done—after all, each object knows what it is. In this case, why can't you ask the chess piece for its set of reachable positions? It turns out you can—by adding constant-specific behavior to the enum:

enum ChessPiece {
    PAWN {
        Set<Position> reachable(Position current) {
            return ChessRules.pawnReachable(current);
        }
    },
    ROOK {
        Set<Position> reachable(Position current) {
            return ChessRules.rookReachable(current);
        }
    },
    // ...
    QUEEN {
        Set<Position> reachable(Position current) {
            return ChessRules.queenReachable(current);
        }
    };

    // declare the methods defined by this enum
    abstract Set<Position> reachable(Position current);
}

This time each named enum constant is followed by a class body that defines the methods for that enum constant—in this case dispatching to the appropriate ChessRules method as before. The advantage of dispatching in the enum constants themselves is that you can't forget to handle the case for a specific value. Looking at the original code for reachable you can see that it would be easy to leave out one of the values, only realizing this at runtime when the error is thrown. Also note that even though we have covered all the possible values in reachable, we still must have that final else clause because the compiler won't check to see if we have covered all possible values and the method must either return a value or throw an exception. By using constant-specific methods we moved the checking for completeness to compile time, which is always a good thing, and we avoided writing code that should never get executed.

The classes that are defined for each enum constant are effectively anonymous inner classes that extend the enclosing enum type—which is why you can't actually declare an enum to be final: Even though it can't be directly extended, it can be implicitly extended by these anonymous inner classes.

As with other anonymous inner classes, the enum constant class body can define arbitrary instance fields, and methods, but it can't declare static members or define constructors. Also note that because enum constants are implicitly static fields, these anonymous inner classes have no enclosing instance.

To be directly accessible, the methods defined for each enum constant must be declared as methods of the enum type itself—either explicitly, as above for reachable, or implicitly by an interface the enum implements. An enum type is allowed to declare an abstract method, only if there is an implementation of that method for each and every enum constant.

Exercise 6.5Redo Exercise 6.4 making getColor an abstract method and defining constant-specific methods for each enum constant to return the correct Color object. Would you recommend using constant-specific methods to do this?

java.lang.Enum

All enum types implicitly extend java.lang.Enum,[3] but no class is allowed to extend Enum directly. Although Enum does provide some methods that can be useful for working with enums, its main role is to establish some useful properties for all enum types:

  • The clone method is overridden to be declared final and to throw CloneNotSupportedException—making it impossible to ever clone an enum instance.

  • The hashCode and equals methods are overridden and declared final—ensuring consistent and efficient hashing for enums, and ensuring that equivalence is the same as identity.

  • The compareTo method of interface java.lang.Comparable is implemented and defined such that enum constants have a natural ordering based on their order of declaration—the first declared enum constant has the lowest position in the ordering.

Enum also provides a toString implementation that returns the name of the enum constant, as we have mentioned, but which you can chose to override. It also provides a finalname method that also returns the name of the enum constant as a String. The difference between these two methods is that name will return the exact name of the enum constant, while toString may be overridden to return a more “user friendly” version of the name—for example, returning "Diamonds" instead of "DIAMONDS".

The additional methods of Enum are:

  • public final int ordinal()

    • Returns the ordinal value of an enum constant. Enum constants are given ordinal values based on the order they are declared: the first declared constant has an ordinal value of zero, the second has an ordinal value of one, etc.

  • public final Class<E> getDeclaringClass()

    • Returns the Class object representing the enum type of this enum constant. This will be different from the value returned by Object.getClass for any enum constant that is set to an anonymous inner class object.

The Enum class also provides a static utility method, valueOf, that takes a class object for an enum type, and a name, and returns the named enum constant from the given enum. If the enum doesn't have a enum constant by that name then IllegalArgumentException is thrown.

In addition to the Enum class, there are two highly efficient enum-specific collection classes: EnumSet defines a set for use with enum values, and EnumMap is a specialized map for which keys are enum values. These specialized collection classes are described further in Chapter 21.

To Enum or Not

The more sophisticated the type you are considering defining as an enum, the more thought you must give to whether enum is the right way to express that type. For simple uses like card suits, days of the week, and even the simple chess piece enum, all that is necessary is that you have a closed set of well-known instances—the decision to define an enum takes almost no thought.

When an enum declares enum constants with constant-specific behavior, additional considerations need to be made. The more sophisticated the behavior of a type, the more likely an application might need to specialize that behavior. Specialization of behavior is done through subclassing, and you cannot subclass an enum. Even something as innocuous as defining a logging, or profiling version of a given enum type is precluded. The same considerations that go into declaring a class final apply to choosing whether to declare a sophisticated enum.

There is little an enum can do that you couldn't do by declaring distinct classes. But the packaging that enum provides is far more convenient to use, though with some restrictions, as you've seen. Additionally, enums are a part of the language and are recognized by other parts of the language. For example, enum constants can be used directly with a switch statement (see page 232) whereas normal object references can not. Unless you need specialization through subclassing, it will almost always be better to use an enum type than to define your own classes.

CENSUS TAKER TO HOUSEWIFE: Did you ever have the measles, and, if so, how many?



[1] The term enum constant refers to those values, like CLUBS and DIAMONDS, declared in the enum type. Enum types can also have normal constants, just like other classes, that are static final fields of various types. We always use “enum constant” to refer to the former, and plain “constant” to refer to the latter.

[2] This example is based on an example given by Tim Peierls.

[3] Enum is actually a generic class defined as Enum<T extends Enum<T>>. This circular definition is probably the most confounding generic type definition you are likely to encounter. We're assured by the type theorists that this is quite valid and significant, and that we should simply not think about it too much, for which we are grateful.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.151.71