Enums Are Classes Too
Many Java programmers only use enums as replacements for those magic numeric constants that represent flags in an application. Back in the day, those constants might be set to unique values from an incrementing sequence. In the more fancier case of combinable flags, they would ascend in powers of 2 that could be and-ed, or-ed, and negated to form a final set of values.
public static final int BAD = -1;
public static final int NEUTRAL = 0;
public static final int GOOD = 1;
From the very first edition of Effective Java, Joshua Bloch’s item #21 proposed that one should rather use the typesafe enum pattern. That is, a final
class with a set of constants enumerating all permitted instances, a private constructor guaranteeing no further instances.
public final class Alignment {
private Alignment() {}
public static final Alignment BAD = new Alignment();
public static final Alignment NEUTRAL = new Alignment();
public static final Alignment GOOD = new Alignment();
}
As there is only one instance of each name in the system (a guarantee you also have to preserve when serializing), those instances can be compared directly by reference using ==
rather than equals
.
In Java 5, Joshua embedded this practice into Java as a language feature:
enum Alignment { BAD, NEUTRAL, GOOD }
You can use enums within switch
statements. For many years, enum constants were the only non-integer constants allowed in case
labels.
The JDK also offers some helper classes, such as EnumSet
, that provide a number of useful methods to resemble bit-operation magic with numeric constants.
Something many developers don’t know is that, under the hood, enums are almost regular classes, a built-in version of the typesafe enum pattern. If you check the implementations of methods like values
or valueOf
there is a little magic, most of which sits in the parent class java.lang.Enum
.
Each enum brings two instance methods: ordinal
and name
. The former returns the internal id of the enum, which changes if you reorder, insert, or remove entries. Whereas name
(and toString
) provide the String
representation of the enum’s instance name.
Being classes, enums can do some neat things.
For example, they can carry information in possibly immutable instance fields. Just add a constructor to the enum class and pass the initial value to each instance. The fields can be public or accessible via a method.
enum Alignment {
BAD(false), NEUTRAL(true), GOOD(true);
public final boolean canHeal;
private Alignment(boolean canHeal) {
this.canHeal = canHeal;
}
}
Enums can also implement interfaces. The simplest being a Named
interface that has just one method: String name()
. Luckily each enum already provides that method, so we can use them in any place expecting aNamed
.
If there are more methods to be implemented in the enum, you can chose to either implement the methods in the enum class or provide abstract methods that have to be overridden in all instances.
enum Alignment implements Inverse {
BAD() {
public Alignment inverse() { return GOOD; }
},
NEUTRAL() {
public Alignment inverse() { return this; }
},
GOOD() {
public Alignment inverse() { return BAD; }
};
public abstract Alignment inverse();
}
interface Inverse {
Alignment inverse();
}
In this regard, enums can be considered as abstract data types. They are also a perfect fit for polymorphic discharge of duties, for instance in the State pattern.
Enums are a flexible and useful but underused tool in the Java language. There’s more magic to them than just being replacements for magic numbers.