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