Enumerated types (enums) are a way to define fixed set of constants, so helpful in many areas of software development. In the most common case, in your Java code written for Java 5 or newer, you will use enums for int
constants and replace chunks of code that look like this
public static final int DIRECTION_NORTH = 0;
public static final int DIRECTION_SOUTH = 1;
public static final int DIRECTION_EAST = 2;
public static final int DIRECTION_WEST = 3;
with something like this
public enum Direction {
NORTH, SOUTH, EAST, WEST;
}
Then, you’ll change method definitions that look like
public void changeDirection(int direction) {
// do something
}
with something like this
public void changeDirection(Direction direction) {
// do something
}
And finally, the method call will be changed too, from
ship.changeDirection(DIRECTION_EAST);
to
ship.changeDirection(Direction.NORTH);
There are a few obvious benefits from using enums over the standard int
constants, such as the type safety and namespaces for example.
But we can also use enums to define other types of constants, string constants for example. You can often see examples of using enums to make a switch
idiom for strings possible in Java. Take a look at the following example
String direction = "WEST";
switch (Direction.valueOf(direction)) {
case WEST : System.out.println("Go west!");
break;
case EAST : System.out.println("Go east!");
break;
default : System.out.println("Go somewhere!");
}
It prints Go west! as a result which is great.
But in order to be truly useful for string constants purpose, enums needs some extra tuning and here’s why.
As the JavaDoc of the valueOf
method used above says:
The name must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.)
So if you want your switch work well you need to be sure that you’ve passed a string with the value exact to some of the identifiers defined in the enum. So if you change your direction
variable value to west
, you’ll get the following exception
Exception in thread "main" java.lang.IllegalArgumentException:
No enum const class net.scriptinginjava.test.EnumTest$Direction.west
at java.lang.Enum.valueOf(Enum.java:192)
at net.scriptinginjava.test.EnumTest$Direction.valueOf(EnumTest.java:1)
at net.scriptinginjava.test.EnumTest.main(EnumTest.java:28)
which could be tricky in some situations.
Another scenario where enums could benefit from extra customization is when you have to have a kind of string mapping between identifiers and their values. Take for example that you want to deal with HTTP headers in this way. You cannot use names like if-modified-since
as an enum identifier since it is not a regular Java variable name so some kind of transformation is needed and the question is, what can we do to customize enums?
As we have seen earlier enums extend java.lang.Enum
class so we can start looking there for enhancements we need. In order to customize represenation of strings in our enum, we need to pay attention to two methods:
toString
– which prints the value of a constant. By default, this method returns the exact name of the constant identifier, just as thename
method.valueOf
– which returns a enum value from a string. Unfortunately, this method cannot be overridden so we have to find a workaround for converting custom strings to enum values without usingvalueOf
method.
Now, take a look at the following enum declaration
public enum HttpHeader {
IF_MODIFIED_SINCE, USER_AGENT, UNKNOWN;
public String toString() {
return name().replaceAll("_", "-").toLowerCase();
}
public static HttpHeader getValue(String value) {
try {
return valueOf(value.replaceAll("-", "_").toUpperCase());
} catch (Exception e) {
return UNKNOWN;
}
}
}
The first thing to notice is that we defined uppercased values with “_” char as a word separator. It’s kind of a “good practice” so let it be. Now take a look at the toString
method, which replaces _ with – in the name and returns such modified value. So, the following line
System.out.println(HttpHeader.IF_MODIFIED_SINCE);
will print if-modified-since
value.
Now let’s see how to use the switch
statement with this enum. Take a look at this example
String[] headers = new String[]{"if-modified-since", "User-Agent", "test"};
for (String header : headers) {
switch (HttpHeader.getValue(header)) {
case IF_MODIFIED_SINCE :
System.out.println("if-modified-since header found");
break;
case USER_AGENT :
System.out.println("user-agent header found");
break;
case UNKNOWN :
default :
System.out.println("unkown header found: " + header);
}
}
It will print the following output
if-modified-since header found
user-agent header found
unkown header found: test
As you can see, this enum has a greater flexibility in matching strings to enum constants. Also, you can note that now we can gracefully fail if the passed string does not match any value. It returns the UNKNOWN
value instead of throwing an exception. Of course, whether this is a desirable funcionality depends on your requirements and design.
In the previous example, we used a simple transformation in the toString
method to customize the value of the printed constant. In case that you have needs for more complex transformation you can apply another technique. Take a look at this example:
public enum HttpHeader {
IF_MODIFIED_SINCE("If-Modified-Since"),
USER_AGENT("User-Agent"),
UNKNOWN("");
private String realName;
private HttpHeader(String realName) {
this.realName = realName;
}
public String toString() {
return realName;
}
public static HttpHeader getValue(String value) {
try {
return valueOf(value.replaceAll("-", "_").toUpperCase());
} catch (Exception e) {
return UNKNOWN;
}
}
}
This enum definition defines a constructor with a string parameter. We can use this parameter to define a string representation of the constant. So now, the code snippet
System.out.println(HttpHeader.IF_MODIFIED_SINCE);
will print the following output
If-Modified-Since
Everything else remains the same as in our previous examples.
Customized like this, enums could be used for dealing with string constants, which could be helpful in processing header names (and values) for example.