How to use typesafe enums in Java

Spread the love

Use typesafe enums to avoid the problems with traditional enumerated types

How to use typesafe enums in Java

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.

download

Download the source code for examples in this Java 101 tutorial. Created by Jeff Friesen for JavaWorld/InfoWorld.

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 to Enum. For example, Coin’s header specifies Enum<Coin>.
  • The actual type argument must be a subclass of Enum. For example, Coin is a subclass of Enum.
  • A subclass of Enum (such as Coin) 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 because equals() 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:

Use typesafe enums to avoid the problems with traditional enumerated types

  • compareTo() compares the current constant with the constant passed as an argument to see which constant precedes the other constant in the enum and returns a value indicating their order. This method makes it possible to sort an array of unsorted constants.
  • getDeclaringClass() returns the java.lang.Class object corresponding to the current constant’s enum. For example, the Class object for Coin, in enum Coin { PENNY,
    NICKEL, DIME, QUARTER}
    , is returned when calling Coin.PENNY.getDeclaringClass(). (I’ll discuss Class in a future Java 101 article.)
  • name() returns the constant’s name. Unless overridden to return something more descriptive, toString() also returns the constant’s name.
  • ordinal() returns a zero-based ordinal, an integer that identifies the constant’s position in the typesafe enum. This integer is used by compareTo(). For example, in the previous Direction.NORTH.compareTo(Direction.SOUTH) expression, compareTo() returned -3 because NORTH has ordinal 0, SOUTH has ordinal 3, and 0 – 3 (which is what compareTo() in this context evaluates) equals -3.

Enum also provides the public static <T extends Enum<T>> T valueOf(Class<T>
enumType, String name)
method for returning the constant from the specified typesafe enum with the specified name:

  • enumType identifies the Class object of the enum from which to return a constant.
  • name identifies the name of the constant to return.

For example, Coin penny = Enum.valueOf(Coin.class, "PENNY"); assigns the Coin constant whose name is PENNY to penny.

Finally, Enum’s Java documentation doesn’t include a values() method because the compiler manufactures this method while generating the typesafe enum’s class file.

You should get into the habit of using typesafe enums instead of traditional enumerated types. Also, get into the habit of using lambdas instead of anonymous inner classes. I discuss lambdas in my next Java 101 article.

Jeff Friesen

Join Geezgo for free. Use Geezgo\’s end-to-end encrypted Chat with your Closenets (friends, relatives, colleague etc) in personalized ways.>>

 254 

You may also like...

Leave a Reply