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&lt;Object&gt;, 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

This site does not intentionally use tracking cookies. Any cookies have been added by my hosting provider (InMotion Hosting), and I have no ability to remove them. I do, however, have access to the site's access logs with source IP addresses.