Level: Introductory
Brett McLaughlin ([email protected]), Author/Editor, O'Reilly Media, Inc.
09 Nov 2004
One of the great new features in Tiger is the enumeration construct, a new type that allows you to represent specific pieces of data as constants, all in a type-safe manner. Tiger expert and frequent developerWorks contributor Brett McLaughlin explains what an enumeration is, how you can use it in your programs, and why it will allow you to dump all that old public static final
code.
<script language=javascript type=text/javascript> // </script>
You already know that the two fundamental building blocks of Java code are classes and interfaces. Now Tiger has introduced one more: the enumeration. Usually referred to simply as an enum, this new type allows you to represent specific data points that accept only certain sets of pre-defined values at assignment time.
Of course, well-practiced programmers already know you can achieve this functionality with static constants, as shown in Listing 1:
Listing 1. Public static final constants |
I'd like to thank O'Reilly Media, Inc., which has permitted me to use the code sample from the "Enumerations" chapter of my book Java 1.5 Tiger: A Developer's Notebook for this article (see Resources).
You can then set up classes to take in constants like
OldGrade.B
, but when doing so keep in mind that such constants are Java
int
s, which means the method will accept any int, even if it doesn't correspond to a specific grade defined in
OldGrade
. Therefore, you'll need to check for upper and lower bounds, and probably include an
IllegalArgumentException
if an invalid value appears. Also, if another grade is eventually added (for example,
OldGrade.WITHDREW_PASSING
), you'll have to change the upper bound on all your code to allow for this new value.
In other words, while using classes with integer constants like this might be a passable solution, it's not a very efficient one. Fortunately, enumerations offer a better way.
|
Defining an enum
Listing 2 uses an enumeration to provide similar functionality to Listing 1:
Listing 2. Simple enumerated type |
Here I've used the new keyword
enum
, given the enum a name, and specified the allowed values.
Grade
then becomes an enumerated type, which you can use in a manner shown in Listing 3:
Listing 3. Using an enumerated type |
By creating a new enumeration (
grade
) of the previously defined type, you can then use it like any other member variable. Of course, the enumeration can be assigned only one of the enumerated values (for example,
A
,
C
, or
INCOMPLETE
). Also, notice how there is no error checking code or boundary considerations in
assignGrade()
.
|
Working with enumerated values
The examples you've seen so far have been rather simple, but enumerated types offer much more. Enumerated values, which you can iterate over and use in
switch
statements, among other things, are very valuable.
Iterating over enums
Let's begin with an example that shows how to run through the values of any enumerated type. This technique, shown in Listing 4, is handy for debugging, quick printing tasks, and loading enums into a collection (which I'll talk about shortly):
Listing 4. Iterating over enumerated values |
Running this snippet of code will give you the output shown in Listing 5:
Listing 5. Output of iteration |
There's a lot going on here. First, I'm using Tiger's new
for/in
loop (also called
foreach
or
enhanced for
). Additionally, you can see that the
values()
method returns an array of individual
Grade
instances, each with one of the enumerated type's values. In other words, the return type of
values()
is
Grade[]
.
Switching on enums
Being able to run through the values of an enum is nice, but even more important is the ability to make decisions based on an enum's values. You could certainly write a bunch of
if (grade.equals(Grade.A))
-type statements, but that's a waste of time. Tiger has conveniently added enum support to the good old
switch
statement, so it's easy-to-use and fits right in with what you already know. Listing 6 shows you how to pull this off:
Listing 6. Switching on enums |
Here the enumerated value is passed into the
switch
statement (remember,
getGrade()
returns an instance of
Grade
) and each
case
clause deals with a specific value. That value is supplied without the enum prefix, which means that instead of writing
case Grade.A
you need to write
case A
. If you don't do this the compiler won't accept the prefixed value.
You should now understand the basic syntax involved in using
switch
statements, but there are still a few things you need to know.
Planning ahead with switch
You can use the
default
statement with enums and switches, just as you would expect. Listing 7 illustrates this usage:
Listing 7. Adding a default block |
Consider the code above and realize that any enumerated value not specifically processed by a
case
statement is instead processed by the
default
statement. This is a technique you should always employ. Here's why: Suppose that the
Grade
enum was changed by another programmer in your group (who of course forgot to tell you about it) to the version shown in Listing 8:
Listing 8. Adding values to the Grade enum |
Now, if you used this new version of
Grade
with the code in Listing 6, these two new values would be ignored. Even worse, you wouldn't even see an error! In these cases, having some sort of general purpose
default
statement is very important. Listing 7 may not handle these values gracefully, but it will give you some indication that values have snuck in, and that you need to address them. Once you've done that you'll have an application that continues to run, doesn't ignore values, and that even even instructs you to take later action. Now that's good coding.
|
Enums and collections
Those of you familiar with the
public static final
approach to coding have probably already moved on to using enumerated values as keys to maps. For the rest of you who don't know what this means, take a look at Listing 9, which is an example of common error messages that can pop up when working with Ant build files:
Listing 9. Ant status codes |
Having some human-readable error message assigned to each status code would allow you to look up the appropriate error message and echo it back out to the console when Ant supplies one of the codes. This is a great use-case for a
Map
, in which each key of the
Map
is one of these enumerated values, and each value is the error message for that key. Listing 10 illustrates how this works:
Listing 10. Maps of enums |
This code uses both generics (see Resources) and the new
EnumMap
construct to create a new map. Also, an enumerated type is supplied via its
Class
object, along with the type of the Map's values (in this case, simple strings). The output of this method is shown in Listing 11:
|
|
|
Going further
Enums can also be used in conjunction with sets, and much like the new
EnumMap
construct, Tiger supplies a new Set implementation
EnumSet
that allows you to work with bitwise operators. Additionally, you can add methods to your enums, use them to implement interfaces, and define what are called value-specific class bodies, where specific code is attached to a particular value of an enum. These features are beyond the scope of this article, but they're well documented elsewhere (see Resources).
|
Use them, but don't abuse them
One of the dangers of learning a new version of any language is the tendency to go crazy with new syntactical structures. Do that and suddenly your code is 80 percent generics, annotations, and enumerations. So use enumerations only where they make sense. Where, then, do they make sense? As a general rule, anywhere constants are in use, such as in places you are currently using the
switch
code to switch constants. If it's a single value (for example, an upper bound on a shoe size, or the maximum number of monkeys that can fit in a barrel), leave the constant as it is. But if you're defining a set of values, and any one of those values can be used for a certain data type, enumerations should be a perfect fit.
Resources
- Download Tiger and try it out for yourself.
- The official J2SE 5.0 home page is a comprehensive resource you won't want to miss.
- For specifics on Tiger, see John Zukowski's Taming Tiger series, which offers short tips on the additions and changes in J2SE 5.0.
- Brett McLaughlin also contributed a two-part series on annotations in Tiger: Annotations in Tiger, Part 1: Add metadata to Java code; and Annotations in Tiger, Part 2: Custom annotations.
- Java 1.5 Tiger: A Developer's Notebook (O'Reilly & Associates; 2004), by Brett McLaughlin and David Flanagan, covers almost all of Tiger's newest features -- including annotations -- in a code-centric, developer-friendly format.
- Find hundreds of articles about every aspect of Java programming in the developerWorks Java technology zone.
- Browse for books on these and other technical topics.
About the author
Brett McLaughlin has worked in computers since the Logo days (remember the little triangle?) with such companies as Nextel Communications and Lutris Technologies. In recent years he has become one of the most well-known authors and programmers in the Java and XML communities. His most recent book, Java 1.5 Tiger: A Developer's Notebook, is the first book available on the newest version of Java, and his classic Java and XML remains one of the definitive works on using XML technologies in Java. |