Enumerations: Why? When?

These are the main arguments for enum, EnumMap, and EnumSet by short examples.

The case for enum

As of Java 6, java.util.Calendar is an example of a messy class that could’ve benefited a lot from using enum (among other improvements).

Currently Calendar defines the following constants (among many others):

// int constant antipattern from java.util.Calendar
public static final int JANUARY = 0;
public static final int FEBRUARY = 1;
...
public static final int SUNDAY = 1;
public static final int MONDAY = 2;
...

These are all int, even though they obviously represent different conceptual entities.

The following are some serious consequences:

  • It’s brittle; care must be taken to assign different numbers whenever needed.
    • If by mistake we set MONDAY = 0;, SUNDAY = 0;, then we have MONDAY == SUNDAY
  • There is no namespace and no type-safety, since everything is just an int:
    • We can setMonth(JANUARY), but we can also setMonth(THURSDAY) or setMonth(42)
    • Who knows what set(int,int,int,int,int,int) (a real method!) does!

By contrast, we could have something like this instead:

// Hypothetical enums for a Calendar library
enum Month {
   JANUARY, FEBRUARY, ...
}
enum DayOfWeek {
   SUNDAY, MONDAY, ...
}

Now we never have to worry about MONDAY == SUNDAY (it can never happen!), and since Month and DayOfWeek are different types, setMonth(MONDAY) does not compile.

Additionally, here are some before-and-after codes:

// BEFORE with int constants
for (int month = JANUARY; month <= DECEMBER; month++) {
   ...
}

Here we’re making all sorts of assumptions, e.g. JANUARY + 1 == FEBRUARY, etc. On the other hand, the enum counterpart is both more concise, more readable, and makes less assumptions (and therefore less chance for bugs):

// AFTER with enum
for (Month month : Month.values()) {
   ...
}

The case for instance fields

In Java, enum is a class that has many special properties, but a class nonetheless, allowing you to define instance methods and fields if necessary.

Consider the following example:

// BEFORE: with int constants
public static final int NORTH = 0;
public static final int EAST  = 1;
public static final int SOUTH = 2;
public static final int WEST  = 3;

public static int degreeFor(int direction) {
   return direction * 90; // quite an assumption!
   // must be kept in-sync with the int constants!
}

//...
for (int dir = NORTH; dir <= WEST; dir++) {
   ... degreeFor(dir) ...
}

On the other hand, with enum you can write something like this:

enum Direction {
   NORTH(0), EAST(90), SOUTH(180), WEST(270);
   // so obvious! so easy to read! so easy to write! so easy to maintain!

   private final int degree;
   Direction(int degree)      { this.degree = degree; }
   public int getDegree()     { return degree; }
}

//...
for (Direction dir : Direction.values()) {
   ... dir.getDegree() ...
}

The case for instance methods

Consider the following example:

static int apply(int op1, int op2, int operator) {
   switch (operator) {
      case PLUS  : return op1 + op2;
      case MINUS : return op1 - op2;
      case ...
      default: throw new IllegalArgumentException("Unknown operator!");
   }
}

As shown in previous example, enum in Java can have instance methods, but not only that but each constant can have its own specific @Override as well. This is shown in the following code:

enum Operator {
    PLUS  { int apply(int op1, int op2) { return op1 + op2; } },
    MINUS { int apply(int op1, int op2) { return op1 - op2; } },
    ...
    ;
    abstract int apply(int op1, int op2);
}

The case for EnumMap

Here’s a quote from Effective Java 2nd Edition:

Never derive a value associated with an enum from its ordinal(); store it in an instance field instead. (Item 31: Use instance fields instead of ordinals) It is rarely appropriate to use ordinals to index arrays: use EnumMap instead. The general principle is that application programmers should rarely, if ever, use Enum.ordinal. (Item 33: Use EnumMap instead of ordinal indexing)

Essentially where as before you may have something like this:

// BEFORE, with int constants and array indexing
Employee[] employeeOfTheMonth = ...

employeeOfTheMonth[JANUARY] = jamesBond;

Now you can have:

// AFTER, with enum and EnumMap
Map<Month, Employee> employeeOfTheMonth = ...

employeeOfTheMonth.put(Month.JANUARY, jamesBond);

The case for EnumSet

Power of two int constants are often used e.g. in C++ to denote bit sets. This relies on bitwise operations. An example may look something like this:

public static final int BUTTON_A = 1;
public static final int BUTTON_B = 2;
public static final int BUTTON_X = 4;
public static final int BUTTON_Y = 8;

int buttonState = BUTTON_A | BUTTON_X; // A & X are pressed!

if ((buttonState & BUTTON_B) != 0) {   // B is pressed...
   ...
}

With enum and EnumSet, this can look something like this:

enum Button {
  A, B, X, Y;
}

Set<Button> buttonState = EnumSet.of(Button.A, Button.X); // A & X are pressed!

if (buttonState.contains(Button.B)) { // B is pressed...
   ...
}

References

See also

  • Effective Java 2nd Edition
    • Item 30: Use enum instead of int constants
    • Item 31: Use instance fields instead of ordinals
    • Item 32: Use EnumSet instead of bit fields
    • Item 33: Use EnumMap instead of ordinal indexing

Related questions

Leave a Comment