Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Java Tip 122: Beware of Java typesafe enumerations

Think twice before relying on instance identity

  • Print
  • Feedback
Departing from traditional practice for JavaWorld's Tips 'N Tricks column, I will talk about when not to use a previously suggested trick. Specifically, the typesafe enum construct, covered in JDC Tech Tips and other publications, can sometimes be hazardous to your code.

Because Java lacks a proper C/C++ enumeration (enum) feature, Java programmers have opted to define simple sets of primitive values:

public class Colors
{
    public static final int GREEN = 0;
    public static final int RED = 1;
    ...
}


This is not particularly typesafe, but it works. You can easily copy and serialize these constants, and then use them for fast switch lookups and so on. In fact, this is how Java language designers originally advised Java programmers to handle Java's lack of an enumeration feature (see "The Java Language Environment" whitepaper).

How it's supposed to work

The typesafe Java enum concept basically replaces the set of primitive constants above with a set of static final object references encapsulated in a class that (possibly) restricts further instantiation. A basic example would be:

public final class Enum
{
    public static final Enum TRUE = new Enum ();
    public static final Enum FALSE = new Enum ();

private Enum () {}
} // end of class


Because the set of instances is restricted by the private constructor and Enum class being final, we can assume that Enum.TRUE and Enum.FALSE are the only instances of the Enum class. Thus, we can use the identity comparison (==) operator instead of the equals() method when comparing enum values. The cost of using the == operator equates to directly comparing pointer values in C/C++. Great, right? We have both type and range safety for enum values while keeping value comparisons efficient.

Is that enough?

Alas, the simple Enum class above lacks a few features. One missing feature is that we cannot pass instances of our Enum class as an argument to an RMI (remote method invocation) or EJB (Enterprise JavaBeans) method. To do that, we must mark the class Serializable:

public final class Enum implements java.io.Serializable
{
    public static final Enum TRUE = new Enum ();
    public static final Enum FALSE = new Enum ();

private Enum () {}
} // end of class


Ok, so what is wrong with that?

The above has a subtle trap, as shown here:

    ByteArrayOutputStream bout = new ByteArrayOutputStream ();
    ObjectOutputStream out = new ObjectOutputStream (bout);

Enum e1 = Enum.TRUE; out.writeObject (e1); out.flush ();
ByteArrayInputStream bin = new ByteArrayInputStream (bout.toByteArray ()); ObjectInputStream in = new ObjectInputStream (bin);
Enum e2 = (Enum) in.readObject (); System.out.println ((e2 == Enum.TRUE || e2 == Enum.FALSE));


This code will print out false, indicating that e2 is neither Enum.TRUE nor Enum.FALSE. This happens because deserializing an object creates a new object without regard to the class's constructors -- the instantiation protection that we thought we got from making the Enum constructor private doesn't affect deserialization.

  • Print
  • Feedback

Resources