Enums Are Classes Too

Michael Hunger
97 Things
Published in
3 min readDec 16, 2019

--

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.

--

--

Michael Hunger
97 Things

A software developer passionate about teaching and learning. Currently working with Neo4j, GraphQL, Kotlin, ML/AI, Micronaut, Spring, Kafka, and more.