Java code that uses traditional enumerated types is problematic. Java 5 gave us a better alternative in the form of typesafe enums. In this article, I introduce you to enumerated types and typesafe enums, show you how to declare a typesafe enum and use it in a switch statement, and discuss customizing a typesafe enum by adding data and behaviors. I wrap up the article by exploring the java.lang.Enum<E extends Enum<E>>
class.
From enumerated types to typesafe enums
An enumerated type specifies a set of related constants as its values. Examples include a week of days, the standard north/south/east/west compass directions, a currency’s coin denominations, and a lexical analyzer’s token types.
Enumerated types have traditionally been implemented as sequences of integer constants, which is demonstrated by the following set of direction constants:
static final int DIR_NORTH = 0; static final int DIR_WEST = 1; static final int DIR_EAST = 2; static final int DIR_SOUTH = 3;
There are several problems with this approach:
- Lack of type safety: Because an enumerated type constant is just an integer, any integer can be specified where the constant is required. Furthermore, addition, subtraction, and other math operations can be performed on these constants; for example,
(DIR_NORTH + DIR_EAST) / DIR_SOUTH
), which is meaningless. - Namespace not present: An enumerated type’s constants must be prefixed with some kind of (hopefully) unique identifier (e.g.,
DIR_
) to prevent collisions with another enumerated type’s constants. - Brittleness: Because enumerated type constants are compiled into class files where their literal values are stored (in constant pools), changing a constant’s value requires that these class files and those application class files that depend on them be rebuilt. Otherwise, undefined behavior will occur at runtime.
- Lack of information: When a constant is printed, its integer value outputs. This output tells you nothing about what the integer value represents. It doesn’t even identify the enumerated type to which the constant belongs.
You could avoid the “lack of type safety” and “lack of information” problems by using java.lang.String
constants. For example, you might specify static final String DIR_NORTH = "NORTH";
. Although the constant value is more meaningful, String
-based constants still suffer from “namespace not present” and brittleness problems. Also, unlike integer comparisons, you cannot compare string values with the ==
and !=
operators (which only compare references).
These problems caused developers to invent a class-based alternative known as Typesafe Enum. This pattern has been widely described and critiqued. Joshua Bloch introduced the pattern in Item 21 of his Effective Java Programming Language Guide (Addison-Wesley, 2001) and noted that it has some problems; namely that it is awkward to aggregate typesafe enum constants into sets, and that enumeration constants can’t be used in switch
statements.
Consider the following example of the typesafe enum pattern. The Suit
class shows how you might use the class-based alternative to introduce an enumerated type that describes the four card suits (clubs, diamonds, hearts, and spades):
public final class Suit // Should not be able to subclass Suit. { public static final Suit CLUBS = new Suit(); public static final Suit DIAMONDS = new Suit(); public static final Suit HEARTS = new Suit(); public static final Suit SPADES = new Suit(); private Suit() {} // Should not be able to introduce additional constants. }
To use this class, you would introduce a Suit
variable and assign it to one of Suit
’s constants, as follows:
Suit suit = Suit.DIAMONDS;
You might then want to interrogate suit
in a switch
statement like this one:
switch (suit) { case Suit.CLUBS : System.out.println("clubs"); break; case Suit.DIAMONDS: System.out.println("diamonds"); break; case Suit.HEARTS : System.out.println("hearts"); break; case Suit.SPADES : System.out.println("spades"); }
However, when the Java compiler encounters Suit.CLUBS
, it reports an error stating that a constant expression is required. You might try to address the problem as follows:
switch (suit) { case CLUBS : System.out.println("clubs"); break; case DIAMONDS: System.out.println("diamonds"); break; case HEARTS : System.out.println("hearts"); break; case SPADES : System.out.println("spades"); }
However, when the compiler encounters CLUBS
, it will report an error stating that it was unable to find the symbol. And even if you placed Suit
in a package, imported the package, and statically imported these constants, the compiler would complain that it cannot convert Suit
to int
when encountering suit
in switch(suit)
. Regarding each case
, the compiler would also report that a constant expression is required.
Java doesn’t support the Typesafe Enum pattern with switch
statements. However, it did introduce the typesafe enum language feature to encapsulate the pattern’s benefits while resolving its issues, and this feature does support switch
.
Declaring a typesafe enum and using it in a switch statement
A simple typesafe enum declaration in Java code looks like its counterparts in the C, C++, and C# languages:
enum Direction { NORTH, WEST, EAST, SOUTH }
This declaration uses the keyword enum
to introduce Direction
as a typesafe enum (a special kind of class), in which arbitrary methods can be added and arbitrary interfaces can be implemented. The NORTH
, WEST
, EAST
, and SOUTH
enum constants are implemented as constant-specific class bodies that define anonymous classes extending the enclosing Direction
class.
Direction
and other typesafe enums extend Enum<E extends Enum<E>>
and inherit various methods, including values()
, toString()
, and compareTo()
, from this class. We’ll explore Enum
later in this article.
Listing 1 declares the aforementioned enum and uses it in a switch
statement. It also shows how to compare two enum constants, to determine which constant comes before the other constant.
Listing 1: TEDemo.java
(version 1)
public class TEDemo { enum Direction { NORTH, WEST, EAST, SOUTH } public static void main(String[] args) { for (int i = 0; i < Direction.values().length; i++) { Direction d = Direction.values()[i]; System.out.println(d); switch (d) { case NORTH: System.out.println("Move north"); break; case WEST : System.out.println("Move west"); break; case EAST : System.out.println("Move east"); break; case SOUTH: System.out.println("Move south"); break; default : assert false: "unknown direction"; } } System.out.println(Direction.NORTH.compareTo(Direction.SOUTH)); } }
Listing 1 declares the Direction
typesafe enum and iterates over its constant members, which values()
returns. For each value, the switch
statement (enhanced to support typesafe enums) chooses the case
that corresponds to the value of d
and outputs an appropriate message. (You don’t prefix an enum constant, e.g., NORTH
, with its enum type.) Lastly, Listing 1 evaluates Direction.NORTH.compareTo(Direction.SOUTH)
to determine if NORTH
comes before SOUTH
.
Compile the source code as follows:
javac TEDemo.java
Run the compiled application as follows:
java TEDemo
You should observe the following output:
NORTH Move north WEST Move west EAST Move east SOUTH Move south -3
The output reveals that the inherited toString()
method returns the name of the enum constant, and that NORTH
comes before SOUTH
in a comparison of these enum constants.
Adding data and behaviors to a typesafe enum
You can add data (in the form of fields) and behaviors (in the form of methods) to a typesafe enum. For example, suppose you need to introduce an enum for Canadian coins, and that this class must provide the means to return the number of nickels, dimes, quarters, or dollars contained in an arbitrary number of pennies. Listing 2 shows you how to accomplish this task.
Listing 2: TEDemo.java
(version 2)
enum Coin { NICKEL(5), // constants must appear first DIME(10), QUARTER(25), DOLLAR(100); // the semicolon is required private final int valueInPennies; Coin(int valueInPennies) { this.valueInPennies = valueInPennies; } int toCoins(int pennies) { return pennies / valueInPennies; } } public class TEDemo { public static void main(String[] args) { if (args.length != 1) { System.err.println("usage: java TEDemo amountInPennies"); return; } int pennies = Integer.parseInt(args[0]); for (int i = 0; i < Coin.values().length; i++) System.out.println(pennies + " pennies contains " + Coin.values()[i].toCoins(pennies) + " " + Coin.values()[i].toString().toLowerCase() + "s"); } }
Listing 2 first declares a Coin
enum. A list of parameterized constants identifies four kinds of coins. The argument passed to each constant represents the number of pennies that the coin represents.
The argument passed to each constant is actually passed to the Coin(int valueInPennies)
constructor, which saves the argument in the valuesInPennies
instance field. This variable is accessed from within the toCoins()
instance method. It divides into the number of pennies passed to toCoin()
’s pennies
parameter, and this method returns the result, which happens to be the number of coins in the monetary denomination described by the Coin
constant.
At this point, you’ve discovered that you can declare instance fields, constructors, and instance methods in a typesafe enum. After all, a typesafe enum is essentially a special kind of Java class.
The TEDemo
class’s main()
method first verifies that a single command-line argument has been specified. This argument is converted to an integer by calling the java.lang.Integer
class’s parseInt()
method, which parses the value of its string argument into an integer (or throws an exception when invalid input is detected). I’ll have more to say about Integer
and its cousin classes in a future Java 101 article.
Moving forward, main()
iterates over Coin
’s constants. Because these constants are stored in a Coin[]
array, main()
evaluates Coin.values().length
to determine the length of this array. For each iteration of loop index i
, main()
evaluates Coin.values()[i]
to access the Coin
constant. It invokes each of toCoins()
and toString()
on this constant, which further proves that Coin
is a special kind of class.
Compile the source code as follows:
javac TEDemo.java
Run the compiled application as follows:
java TEDemo 198
You should observe the following output:
198 pennies contains 39 nickels 198 pennies contains 19 dimes 198 pennies contains 7 quarters 198 pennies contains 1 dollars
Exploring the Enum<E extends Enum<E>>
class
The Java compiler considers enum
to be syntactic sugar. Upon encountering a typesafe enum declaration, it generates a class whose name is specified by the declaration. This class subclasses the abstract Enum<E extends Enum<E>>
class, which serves as the base class for all typesafe enums.
Enum
’s formal type parameter list looks ghastly, but it’s not that hard to understand. For example, in the context of Coin extends Enum<Coin>
, you would interpret this formal type parameter list as follows:
- Any subclass of
Enum
must supply an actual type argument toEnum
. For example,Coin
’s header specifiesEnum<Coin>
. - The actual type argument must be a subclass of
Enum
. For example,Coin
is a subclass ofEnum
. - A subclass of
Enum
(such asCoin
) must follow the idiom that it supplies its own name (Coin
) as an actual type argument.
Examine Enum
’s Java documentation and you’ll discover that it overrides java.lang.Object
‘s clone()
, equals()
, finalize()
, hashCode()
, and toString()
methods. Except for toString()
, all of these overriding methods are declared final
so that they cannot be overridden in a subclass:
clone()
is overridden to prevent constants from being cloned so that there is never more than one copy of a constant; otherwise, constants could not be compared via==
and!=
.equals()
is overridden to compare constants via their references. Constants with the same identities (==
) must have the same contents (equals()
), and different identities imply different contents.finalize()
is overridden to ensure that constants cannot be finalized.hashCode()
is overridden becauseequals()
is overridden.toString()
is overridden to return the constant’s name.
Enum
also provides its own methods. These methods include the final
compareTo()
(Enum
implements the java.lang.Comparable<T>
interface), getDeclaringClass()
, name()
, and ordinal()
methods: