Implementing Enumerated Types in Java

lthough Java syntax borrowed heavily from C++, there are many C++ featuresthat Java chose to omit. At times, the lack of a particular featuremakes Java programs cumbersome to implement. One such feature isthe ability to define enumerated types, such as those declared with theC++ 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 animplicit ordering. The principal benefit derived from an enumerated typeis the ability to restrict the value of a variable to a discrete set,eliminating the need for explicit range checking and leveragingcompiler type-checking for error detection.

You might represent a set of discrete volume levels in C++ as follows:

enum Volume { SILENT, SOFT, AUDIBLE, LOUD, ELEVEN };

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 requiresthose values either implement the interface, or explicitly referencethe values. For example

public interface Volume {    public static final int SILENT  = 0;    public static final int SOFT    = 1;    public static final int AUDIBLE = 2;    public static final int LOUD    = 3;    public static final int ELEVEN  = 4;}

Here any variable of type integer can represent a volume level.We have caputured the ordering of the values, but have lost bothtype safety and the limitation of the domain to a discreteprogrammer-defined set. In the C++ example, if a variable is oftype Volume, you know that it can only possess one of the five differentvalues allowable to a Volume type. How do we capture this in Java andalso 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, andthen create static final members that are instances of the new type whichrepresent the discrete range. We can change our Java implementation tothe following:

public final class Volume {    public static final Volume SILENT  = new Volume();    public static final Volume SOFT    = new Volume();    public static final Volume AUDIBLE = new Volume();    public static final Volume LOUD    = new Volume();    public static final Volume ELEVEN  = new Volume();    private Volume() {    // Empty private constructor ensures the only objects of    // this type are the enumerated elements declared above.    }                            }public class Stereo {   Volume volume = Volume.SILENT;   ...}

Now we can guarantee that a Stereo instance can only have a Volume equalto one of the discrete levels we have defined (and null). The problemwith this approach is that now we have lost the ordering of the values.We also can no longer manipulate the values as integers, disallowingtheir use in a switch statement. A natural addition would be to add a member variable to Volume defining the ordering of the levelsas follows:

public final class Volume implements Comparable {    public static final int SILENT_LEVEL  = 0;    public static final int SOFT_LEVEL    = 1;    public static final int AUDIBLE_LEVEL = 2;    public static final int LOUD_LEVEL    = 3;    public static final int ELEVEN_LEVEL  = 4;    public static final Volume SILENT  = new Volume(SILENT_LEVEL);    public static final Volume SOFT    = new Volume(SOFT_LEVEL);    public static final Volume AUDIBLE = new Volume(AUDIBLE_LEVEL);    public static final Volume LOUD    = new Volume(LOUD_LEVEL);    public static final Volume ELEVEN  = new Volume(ELEVEN_LEVEL);    private static final Volume __VOLUMES[] = {      SILENT, SOFT, AUDIBLE, LOUD, ELEVEN    };    private int __level;    public static final Volume getVolume(int level) {      if(level >= SILENT_LEVEL && level <= ELEVEN_LEVEL)        return __VOLUMES[level];      return null;    }    private Volume(int level) {      __level = level;    }                            public boolean equals(Object obj) {      // Assume proper type was given      return (__level == ((Volume)obj).__level);    }    public int compareTo(Object obj) {      // Assume proper type was given      int other = ((Volume)obj).__level;      if(__level == other)        return 0;      if(__level < other)        return -1;      return 1;    }    public int getLevel() { return __level; }}

This solves the ordering problem and the manipulation of the values asintegers. You can determine ordering with equals() and compareTo().And if you want to use the values in a switch statement, you can fetcha value to test with getLevel() and use the LEVEL constants as thecases. You may be wondering why we added the getVolume() factory method.If we omit the factory method, a small problem arises with respectto serialization. The Volume class has a private constructor andno setter methods so that we may limit the range of values it can represent.This also makes it unserializable. A serializable class must declareVolume variables as transient, and manually store the getLevel() value.On deserialization, the class may recreate its Volume member by usingthe getVolume() factory method. A benefit of this approach is that itallows 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 thefollowing:

private Object readResolve() throws ObjectStreamException {     return __VOLUMES[__level];}

This prevents duplicate constants from being created duringdeserialization.

It is quite a lot of work just to get the functionality of enumeratedtypes. Simply using integer constants will be sufficient for mostprograms, but when value restriction and type safety become issues,you will have to resort to some variation of the more complicatedsolution.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

Recent Articles: