Enum: Not Just a Constant With a Pretty Face
Originally published: 2009-08-31
Last updated: 2015-04-27
The enum
keyword was introduced with JDK 1.5 (Java 5), as
syntactic sugar for creating a subclass of java.lang.Enum
and adding static members to it. This technique, known as the
“typesafe enum pattern,” wasn't new, although before JDK 1.5
it was more work to implement, and homegrown implementations often ran into
problems with serialization.
Perhaps because of the effort involved with homegrown enums, JDK 1.5 enums seem to get little use: even in new development, you'll still find constants defined as static strings and ints. And while there's a place for such constants, it seems to me that these projects are missing out on a lot of benefits. This article looks at some of them.
How to create an enum
If you've never used the enum
keyword before, it's easy:
package com.kdgregory.example.enums; public enum Flavor { COFFEE, VANILLA, CHOCOLATE, STRAWBERRY, RUM_RAISIN, PEACH }
An enum is a class, so its definition looks a lot like any other class definition. Enums may be defined as top-level classes as shown here, or as nested (static) classes. They can be public, private, or package-accessible (default access), but not protected; you can't subclass an enum.
The comma-separated list of names are the enum's members: each becomes a
static member variable of the enum class. If you were to disassemble the
resulting .class
file, you'd see something like this:
public final class Flavor extends java.lang.Enum { public static final Flavor COFFEE = new Flavor("COFFEE", 0); public static final Flavor VANILLA = new Flavor("VANILLA", 1); // ...
You can see that each constant is constructed around two values: a string
containing the constant's name, and an incremented “ordinal”
value that's unique to the instance. This ordinal value is assigned when
the enum is compiled, and adding or removing entries will change the value
of a particular entry (later we'll take a closer look at this). It allows the
enum to be used in a switch
statement, and also allows
entries to be compared based on the order they're defined rather than by
their string values (eg, VANILLA
is “less than”
CHOCOLATE
).
Since the enum elements are normal Java static members, you can write JavaDoc for them — and I highly recommend doing just that. And since an enum is a class, you can declare a method signature that takes or returns enum values:
public void setFlavor(Flavor value) { // ... public Flavor getFlavor() { // ...
Using enums as data values in beans
The previous methods should have gotten you thinking about using enums in
bean-style objects — the POJOs that you might use with Hibernate or
EJB3. And this is a great use, with one twist: hide the enum inside the
bean, and expose the property as a String
.
public class Person { private enum Gender { M, F } private String _firstName; private String _lastName; private Date _birthday; private Gender _gender; // ... public String getGender() { return _gender.toString(); } public void setGender(String value) { _gender = Gender.valueOf(value); } }
Why would anyone want to do this? The main reason is that you get validation for free:
Person person = new Person(); // this is OK person.setGender("M"); // this isn't person.setGender("FILE_NOT_FOUND");
The second call throws IllegalArgumentException
. It
would also throw if you passed in a lowercase "m", so you usually need to
add some code to your setter:
_gender = Gender.valueOf(value.toUpperCase());
The second reason for exposing your enums is strings is more subtle:
persistence packages don't deal well with raw enums. EJB3 gives you the
@Enumerated
annotation, but some other frameworks
require you to write a conversion handler. You avoid the whole problem if
you expose your enums as strings.
You'll note that I defined the enum as private
. This is
for encapsulation: the enum exists solely to manage internal state for the
POJO, so there's no reason to expose it to the outside world. If you decide
that a particular enum should be used by application code (eg, in a
switch
statement), consider moving it out of the bean
rather than exposing it as a public
nested class.
Using enums instead of string literals
Most enum examples use uppercase for the enum's members, in keeping with the Sun coding standards for constants. But enum members can be any legal Java identifier. Here's an excerpt from an enum that I defined for a web service (the original has extensive JavaDoc for each member):
public enum Operation { RetrieveList, DeleteList, AddItem, // ...
Here again I'm relying on the easy conversion between enums and strings: when
building a URL, I can simply concatenate the enum value. And on the service
side, I can use valueOf()
to parse the name and get automatic
validation. The alternative, a static string constant, doesn't give me any of
this. Of course, this technique only works if the strings are indeed valid
Java identifiers, but you'll find that to be true in many cases.
Attaching attributes to enums
Enums are classes, which means they have constructors. One little-known and less-used feature is that you can pass data to that constructor. Here's my operation enum again, showing the member definitions with their attributes and the methods to access those attributes:
public enum Operation { RetrieveList(HttpMethod.GET, null), DeleteList(HttpMethod.POST, null), AddItem(HttpMethod.POST, ProductEntry.class), // ... private HttpMethod _method; private Class<?> _requestDTOklass; private Operation(HttpMethod method, Class<?> requestDTOClass) { _method = method; _requestDTOklass = requestDTOClass; } public boolean isPost() { return _method == HttpMethod.POST; } public Class<?> getRequestDTOClass() { return _requestDTOklass; } }
So now, not only can I validate operation URL names, I can also validate the
HTTP method. And I know what class I should use to convert the POST body into
a data transfer object. And yes, HttpMethod
is itself an
enum.
Perhaps most important, I've also documented the expected behavior of these operations, in a way that won't become stale.
Using varargs and enums for configuration
Shifting gears, have you ever seen — or written — a factory method or constructor like this? I have (both written and seen). And no matter how much JavaDoc you have, or how logical the parameter order, sooner or later you're going to switch two parameters and waste time tracking down the resulting bug.
public BadConstructor( boolean addXsiType, boolean introspectMaps, boolean useXsdFormat, boolean useXsiNil) { // do something with all of those parameters }
You didn't have to write code like this before JDK 1.5, but the correct code was painful (usually involving a configuration object). With JDK 1.5 you can combine enums and varargs and get rid of the mess:
public enum Option { ADD_XSI_TYPE, INTROSPECT_MAPS, USE_XSD_FORMAT, USE_XSI_NIL } private EnumSet<Option> _options = EnumSet.noneOf(Option.class); public GoodConstructor(Option... options) { for (Option option : options) _options.add(option); }
Of course, this only works when you have boolean
options,
and when the default value is false
. However, I find that
knowing this technique drives me toward configurable objects that fit
exactly those constraints.
EnumMap and EnumSet, are they useful?
Along with enums, JDK 1.5 provided two new collections classes:
EnumSet and
EnumMap.
The JDK doc describes these classes as “extremely compact and
efficient,” which I suppose is a good if you have limited memory,
but that's rarely the case. They're also rather painful to use, particularly
EnumSet
, which has numerous static factory methods
— except one that takes only varargs (in the developers' defense, I
can understand why they did this: it's more efficient if you have a
single-element constructor).
Despite their drawbacks, these classes have one overwhelming reason for
use: they're
typesafe. You can pass an EnumSet
into a method
that expects Set<Object>
, and it will throw if
that method tries to store anything other than an instance of the enum. If
you're using enums for validation and control, then this feature is worth
the pain.
Warning: don't use ordinal values — ever
In the top of this article, I noted that each member of an enum has an ordinal value that's assigned when the enum is compiled. But if you add or move enum members, their ordinal values will change. If your code saves the ordinal value in a database, you've just created a bug.
I've seen code that does this, particularly from people who are used to C's
enum type (where the enum is truly just a convenience for creating constants).
I can't think of a single reason to actually use the ordinal value, except
for comparisons, and enum implements Comparable
.
So I simply pretend that ordinal values don't exist, and have managed to
remain bug-free (at least, free of bugs caused by ordinal values).
There's still a role for static constants
While enums can substitute for static constants in most situations, their sweet spot is type-safety and validation: defining a bounded set of legal values, and ensuring the use of those values. If you have an unbounded set of values, or are using constants to prevent typos, then an enum probably won't buy you anything.
For example, I have a MimeTypes
class that contains
constants for (you guessed it) registered MIME content types. I'm not trying
to replicate the entire
IANA registry: this class gets updated when I write code that needs
to use a new type. And I'm not trying to use the class to constrain my code:
it contains far more MIME types than are used by a typical application. So
there's no good reason to use an enum over a string constant.
Everything else, however, is fair game.
Copyright © Keith D Gregory, all rights reserved