You are at: http://i.am/rwald / java / articles / the type-safe enumeration idiom


The Type-Safe Enumeration Idiom
an idiom for creating robust, type-constants within Java

Rodney Waldhoff

<rwald@hotmail.com>

July 18, 1999


context

When programming, we often have the need to create an enumeration of a finite list of values, for example, enumerating the states of a state-machine, or, in the running example we'll use here, enumerating the types of HTML headers (<h1>, <h2>, etc.).

C++ offers a fairly robust way to accomplish this enumeration of values, the enum keyword that essentially adds a new primitive type to the language. It is used as follows:

// this is a C++ code sample!
enum HtmlHeadingLevel { H1, H2, H3, H4, H5, H6 };

The enum structure in C++ has a number of useful features:

1. C++ enums are type-safe. This means that one can define methods that only accept one of the enumerated values. E.g.:

// C++ code
void HtmlWriter::openHeading(HtmlHeadingLevel level) {
   //...
}

2. Since C++ enums can be treated as integer values, we can use them as array indices. E.g.,

// C++ code
char*[] openTag = { "<H1>", "<H2>", "<H3>", "<H4>", 
                    "<H5>", "<H6>" };
// ...
htmloutputstream << openTag[level];

3. With a minor addition we can provide a mechanism for iterating through the C++ enum:

// C++ code
enum HtmlHeadingLevel { H1, H2, H3, H4, H5, H6 };
const int MAX_HTML_HEADING_LEVEL = ((int)H6) + 1;

which can be used as:

// C++ code
for(HtmlHeadingLevel i=0;i<MAX_HTML_HEADING_LEVEL;i++) {
   htmloutputstream << openTag[i] << "heading " << i << closeTag[i] << endl;
}

A naive Java approach to this situation would be to declare a collection of final values. E.g.:

// finally, some Java
public class HtmlWriter {
   public static final byte H1 = 0;
   public static final byte H2 = 1;
   public static final byte H3 = 2;
   public static final byte H4 = 3;
   public static final byte H5 = 4;
   public static final byte H6 = 5;
   public static final byte MAX_HTML_HEADING = 6;
   
   protected String[] openTag = { "<H1>", "<H2>", "<H3>", "<H4>", 
                                  "<H5>", "<H6>" };
   
   public void openHeading(byte headingsize) {
     htmloutputstream.write(openTag[headingsize]);
   } 
}

Which would be called from client classes as in:

myhtmlwriter.openHeading(HtmlWriter.H3);

Unfortunately, this approach is not type-safe, in that any byte value could be passed into the openHeading method. The Java approach also shares a flaw with C++ in that when we want to dump the HtmlHeadingLevel value to a string, it appears as an integer value, rather than the mnemonic we use in the code.

problem

We would like an idiom for creating constants in Java that are:

I think you'll find the Java implementation given below to be even more robust than the C++ enum, since it acts as a first-class citizen within the language.

forces

The primary forces pulling on our solution are:

Although the soltuion propsed here makes some assumtions about prioritizing these forces, you are free to mix and match features to meet the needs of your application.

solution

We will build up to these features in an iterative fashion. Feel free to mix, match or extend these implementations according to your particular design needs.

First, let us address the issue of type-safety. Clearly in Java this means we must use a class. (Note that this is essentially the same technique that the java.awt.Color. class uses to define color constants. We simply make the constructor private to prevent new instances from being created.)

public final class HtmlHeadingLevel {
   private HtmlHeadingLevel() {
      // no implementation is needed, yet
   }
   public static final HtmlHeadingLevel H1 = new HtmlHeadingLevel();
   public static final HtmlHeadingLevel H2 = new HtmlHeadingLevel();
   public static final HtmlHeadingLevel H3 = new HtmlHeadingLevel();
   public static final HtmlHeadingLevel H4 = new HtmlHeadingLevel();
   public static final HtmlHeadingLevel H5 = new HtmlHeadingLevel();
   public static final HtmlHeadingLevel H6 = new HtmlHeadingLevel();
}

This makes it possible to define methods that will only accept one of our enumerated values, for example:

public void openHeading(HtmlHeadingLevel level) {
  if( H1.equals(level) ) {
    htmloutputstream.write("<H1>");
  } else if( H2.equals(level) ) {
    htmloutputstream.write("<H2>");
  } else if( H3.equals(level) ) {
    // ...and so on...
  }
}

Which would be accessed from client classes as:

myHtmlWriter.openHeading(HtmlHeadingLevel.H1);

By making the HtmlHeadingLevel constructor private, we make it impossible to create new instances. By making the HtmlHeadingLevel references final, we can guarantee that these references will never change. Hence any non-null value passed to the setHeadingSize method must be one of the six provided values. (This actually allows us to use the == operator to compare the headings rather than equals since in the above implementation there are always exactly seven instances of HtmlHeading per Java Virtual Machine. That approach would be faster, but also less tolerant of design changes. An optimizing compiler might inline the equals method to == anyway, since the default implementation of equals is to use the == operator.)

Note that this approach is not significantly more expensive in time or in space than using a set of final primatives, since the object references are essentially 32 bit values.

Let us now add a mechanism by which we can convert HtmlHeadingLevel references to integers, which may be useful, for example, when working with arrays. We do this in straightforward fashion, assigning int values in the constructor to make it easier to add or remove HtmlHeadingLevel values without mis-numbering the values. We can also use this automatic numbering mechanism to provide a quick count of the values.

public final class HtmlHeadingLevel {
   
   private HtmlHeadingLevel() {
      _intVal = _nextIndex++;
   }

   public final int toInt() {
      return _intVal;
   };

   private final int _intVal;

   public static final int getNumLevels() {
      return _nextIndex;
   };

   public static final HtmlHeadingLevel H1 = new HtmlHeadingLevel();
   public static final HtmlHeadingLevel H2 = new HtmlHeadingLevel();
   public static final HtmlHeadingLevel H3 = new HtmlHeadingLevel();
   public static final HtmlHeadingLevel H4 = new HtmlHeadingLevel();
   public static final HtmlHeadingLevel H5 = new HtmlHeadingLevel();
   public static final HtmlHeadingLevel H6 = new HtmlHeadingLevel();

   private static int _nextIndex = 0;
}

Similiarly, we can add a String representation of the HtmlHeadingLevels to make them easier to print (for example, when debugging). We'll make this a parameter to the constructor.

public final class HtmlHeadingLevel {
   
   private HtmlHeadingLevel(String label) {
      _strVal = label;
      _intVal = _nextIndex++;
   }

   public final int toInt() {
      return _intVal;
   };

   public final String toString() {
      return _strVal;
   };

   private final int _intVal;
   private final String _strVal;

   public static final int getNumLevels() {
      return _nextIndex;
   };

   public static final HtmlHeadingLevel H1 = new HtmlHeadingLevel("H1");
   public static final HtmlHeadingLevel H2 = new HtmlHeadingLevel("H2");
   public static final HtmlHeadingLevel H3 = new HtmlHeadingLevel("H3");
   public static final HtmlHeadingLevel H4 = new HtmlHeadingLevel("H4");
   public static final HtmlHeadingLevel H5 = new HtmlHeadingLevel("H5");
   public static final HtmlHeadingLevel H6 = new HtmlHeadingLevel("H6");

   private static int _nextIndex = 0;
}

Next, it would be useful to add a mechanism for walking through the list of values. We will do this in two ways. First, let's add some linked-list like functionality. This will provide a degree of introspection, allowing us to access the values without knowing what they are in advance.

public final class HtmlHeadingLevel {
   
   private HtmlHeadingLevel(String label) {
      _strVal = label;
      _intVal = _nextIndex++;
      
      if(null == _first) {
        _first = this;
        _last = this;
        _prev = null;
      } else {
        _prev = _last;
        _last._next = this;
        _last = this;
      }
      _next = null;
   }

   public final int toInt() {
      return _intVal;
   };

   public final String toString() {
      return _strVal;
   };

   public final HtmlHeadingLevel getPrev() {
      return _prev;
   };

   public final HtmlHeadingLevel getNext() {
      return _next;
   };

   private final int _intVal;
   private final String _strVal;
   private  HtmlHeadingLevel _prev;
   private  HtmlHeadingLevel _next;

   public static final int getNumLevels() {
      return _nextIndex;
   };

   public static final HtmlHeadingLevel getFirst() {
      return _first;
   };
   
   public static final HtmlHeadingLevel getLast() {
      return _last;
   };

   public static final HtmlHeadingLevel H1 = new HtmlHeadingLevel("H1");
   public static final HtmlHeadingLevel H2 = new HtmlHeadingLevel("H2");
   public static final HtmlHeadingLevel H3 = new HtmlHeadingLevel("H3");
   public static final HtmlHeadingLevel H4 = new HtmlHeadingLevel("H4");
   public static final HtmlHeadingLevel H5 = new HtmlHeadingLevel("H5");
   public static final HtmlHeadingLevel H6 = new HtmlHeadingLevel("H6");

   private static int _nextIndex = 0;
   private static HtmlHeadingLevel _first = null;
   private static HtmlHeadingLevel _last = null;
}

A second way of stepping though the values that may come in handy is to provide an implementation of the java.util.Enumeration interface to access the values. While we're at it, we'll add a mechansim for mapping from the assigned labels to the HtmlHeadingLevel values, which will make it possible to essentially construct the headings from Strings.

public final class HtmlHeadingLevel {
   
   private HtmlHeadingLevel(String label) {
      _strVal = label;
      _intVal = _nextIndex++;
   }

   public final int toInt() {
      return _intVal;
   };

   public final String toString() {
      return _strVal;
   };

   private final int _intVal;
   private final String _strVal;

   public static final int getNumLevels() {
      return _nextIndex;
   };

   public static final HtmlHeadingLevel H1 = new HtmlHeadingLevel("H1");
   public static final HtmlHeadingLevel H2 = new HtmlHeadingLevel("H2");
   public static final HtmlHeadingLevel H3 = new HtmlHeadingLevel("H3");
   public static final HtmlHeadingLevel H4 = new HtmlHeadingLevel("H4");
   public static final HtmlHeadingLevel H5 = new HtmlHeadingLevel("H5");
   public static final HtmlHeadingLevel H6 = new HtmlHeadingLevel("H6");

   private static int _nextIndex = 0;
}

Next, it would be useful to add a mechanism for walking through the list of values. We will do this in two ways. First, let's add some linked-list like functionality. This will provide a degree of introspection, allowing us to access the values without knowing what they are in advance.

public final class HtmlHeadingLevel {
   
   private HtmlHeadingLevel(String label) {
      _strVal = label;
      _intVal = _nextIndex++;
      
      if(null == _first) {
        _first = this;
        _last = this;
        _prev = null;
      } else {
        _prev = _last;
        _last._next = this;
        _last = this;
      }
      _next = null;
      
      _myHash.put(label,this);
   }

   public final int toInt() {
      return _intVal;
   };

   public final String toString() {
      return _strVal;
   };

   public final HtmlHeadingLevel getPrev() {
      return _prev;
   };

   public final HtmlHeadingLevel getNext() {
      return _next;
   };

   private final int _intVal;
   private final String _strVal;
   private  HtmlHeadingLevel _prev;
   private  HtmlHeadingLevel _next;

   public static final int getNumLevels() {
      return _nextIndex;
   };

   public static final HtmlHeadingLevel getFirst() {
      return _first;
   };
   
   public static final HtmlHeadingLevel getLast() {
      return _last;
   };

   public static final java.util.Enumeration elements() {
      return _myHash.elements();
   };
   
   public static final HtmlHeadingLevel forName(String str) {
      return _myHash.get(str);
   };
   

   public static final HtmlHeadingLevel H1 = new HtmlHeadingLevel("H1");
   public static final HtmlHeadingLevel H2 = new HtmlHeadingLevel("H2");
   public static final HtmlHeadingLevel H3 = new HtmlHeadingLevel("H3");
   public static final HtmlHeadingLevel H4 = new HtmlHeadingLevel("H4");
   public static final HtmlHeadingLevel H5 = new HtmlHeadingLevel("H5");
   public static final HtmlHeadingLevel H6 = new HtmlHeadingLevel("H6");

   private static int _nextIndex = 0;
   private static HtmlHeadingLevel _first = null;
   private static HtmlHeadingLevel _last = null;
   private static final java.util.Hashtable _myHash = new java.util.Hashtable();

}

Note that, in general, the order of the elements in the Enumeration returned by the elements method will not be the same as in the linked list. This is typically not an issue.

references

While the idiom described here adds some additional functionality, and provides a singular, relatively concise explination, the basic idea used here is essentially a combination of the ideas expressed in the following articles.

· "Java Tip 27: Type safe constants in C++ and Java" [Bishop, Philip; Eveque Systems Ltd.]
(published in JavaWorld, April 1997, available online at http://www.javaworld.com/javatips/jw-javatip27.html.)
· "Create enumerated constants in Java" [Armstrong, Eric]
(published in JavaWorld, July 1997, available online at http://www.javaworld.com/javaworld/jw-07-1997/jw-07-enumerated.html.)
· "Java Tip 50: On the Parameter Constants pattern" [Steelman, Jon]
(published in JavaWorld, April 1998, available online at http://www.javaworld.com/javaworld/javatips/jw-javatip50.html.)

(Not that any of those authors can literally lay claim to originating these ideas either.)


Care to comment on this article? Answer as many or as few as you choose, and click the send button.

Note that after submitting, you will be returned to this page. Don't worry, your comments have been recorded.

Overall, the article was...
The length of this article was...
The level of detail was...
You can enter any other comments you want in the field below.
Be sure to include your email address if you want a reply.

You are at: http://i.am/rwald / java / articles / the type-safe enumeration idiom