| kdgregory.com | |
|
Blog
Food Programming Travel |
Enum: Not Just a Constant With a Pretty Face The 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
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 a normal class
definition. They may be defined as top-level classes as shown here, or
as nested (static) classes. The comma-separate 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
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 Since the enum elements are normal Java static members, you can write JavaDoc for them — and I highly recommend doing just that, particularly for enums used as configuration options. 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 bean values 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
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 _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
You'll note that I defined the enum as Using enums instead of string literalsMost 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 Attaching attributes to enumsEnums 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(boolean isPost, 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 can drive conversion of the POST body into a data
transfer object. And yes, 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 configurationShifting 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
|