|
|||||||||
lthough Java syntax borrowed heavily from C++, there are many C++ features
that Java chose to omit. At times, the lack of a particular feature
makes Java programs cumbersome to implement. One such feature is
the ability to define enumerated types, such as those declared with the
C++ enum keyword.
An enumerated type can only hold a set of values defined by the programmer. Usually these values behave much like integers, and as such have an implicit ordering. The principal benefit derived from an enumerated type is the ability to restrict the value of a variable to a discrete set, eliminating the need for explicit range checking and leveraging compiler type-checking for error detection. You might represent a set of discrete volume levels in C++ as follows:
The most common way to implement this in Java is to define a set of
static final integers in an interface, and have any class that requires
those values either implement the interface, or explicitly reference
the values. For example
Here any variable of type integer can represent a volume level.
We have caputured the ordering of the values, but have lost both
type safety and the limitation of the domain to a discrete
programmer-defined set. In the C++ example, if a variable is of
type Volume, you know that it can only possess one of the five different
values allowable to a Volume type. How do we capture this in Java and
also retain type safety?
An often used technique to simulate enumarated types in pure object-oriented programming is to declare a new empty class for each enumerated type, and then create static final members that are instances of the new type which represent the discrete range. We can change our Java implementation to the following:
Now we can guarantee that a Stereo instance can only have a Volume equal
to one of the discrete levels we have defined (and null). The problem
with this approach is that now we have lost the ordering of the values.
We also can no longer manipulate the values as integers, disallowing
their use in a switch statement. A natural addition would be to
add a member variable to Volume defining the ordering of the levels
as follows:
This solves the ordering problem and the manipulation of the values as
integers. You can determine ordering with equals() and compareTo().
And if you want to use the values in a switch statement, you can fetch
a value to test with getLevel() and use the LEVEL constants as the
cases. You may be wondering why we added the getVolume() factory method.
If we omit the factory method, a small problem arises with respect
to serialization. The Volume class has a private constructor and
no setter methods so that we may limit the range of values it can represent.
This also makes it unserializable. A serializable class must declare
Volume variables as transient, and manually store the getLevel() value.
On deserialization, the class may recreate its Volume member by using
the getVolume() factory method. A benefit of this approach is that it
allows you to continue to use the == operator for comparison operations,
rather than restrict you to invoking equals(). Another way
to deal with serialization to create a readRsolve() method like the
following:
This prevents duplicate constants from being created during deserialization.
It is quite a lot of work just to get the functionality of enumerated
types. Simply using integer constants will be sufficient for most
programs, but when value restriction and type safety become issues,
you will have to resort to some variation of the more complicated
solution.
|
|||||||||
|
Daniel F. Savarese holds a B.S. in astronomy and an M.S. in computer science, both from the University of Maryland, College Park. He is the author of the OROMatcher regular expression library for Java. Reach him here.
| |||||||||