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

Page 3 of 3

The typesafe enum pattern fails in the above cases for a reason different from serialization intricacies: the same class loaded by different classloaders is, strictly speaking, actually a different class each time. The Enum class's static data will be created anew by each classloader loading the class. Instances of such classes can coexist in the VM, but they will be instances of incompatible types; they could not be cast to each other, and thus, they could not be compared using the == operator.

The following code simulates this runtime scenario. This new class, EnumConsumer, will act as something that uses the Enum type:

public class EnumConsumer implements IEnumConsumer
{
    public Vector getObjects ()
    {
        Vector result = new Vector ();
        result.add (Enum.FALSE);
        result.add (Enum.TRUE);
        return result;
    }

public void validate (Vector objects) { if (objects.get (0) != Enum.FALSE) System.out.println ("element 0 [" + objects.get (0) + "] != Enum.FALSE"); else System.out.println ("element 0 Ok");
if (objects.get (1) != Enum.TRUE) System.out.println ("element 1 [" + objects.get (1) + "] != Enum.TRUE"); else System.out.println ("element 1 Ok"); }
} // end of class


EnumConsumer implements a simple test interface, IEnumConsumer, that we will use to drive EnumConsumer instances across multiple classloader namespaces:

public interface IEnumConsumer
{
    Vector getObjects ();
    void validate (Vector objects);

} // end of interface


The idea here is simple enough: getObjects() returns a Vector of two possible Enum values in known order. If that Vector is passed into validate(), it will check the expected state of data and complain if something is wrong. Naively, I expect that if I execute getObjects() and send the result of that execution into validate() then it should never fail. But it can fail, as shown below. The key to making this interesting is to drive this class from the following main() method:

    public static void main (String [] args) throws Exception
    {
        File loaderClasspathDir = new File ("data");
        loaderClasspathDir.mkdir ();

// move Enum.class and EnumConsumer.class from "./out/" to "./data/": String [] classNames = new String [] {"Enum.class", "EnumConsumer.class"}; for (int c = 0; c < classNames.length; c ++) { File source = new File ("out", classNames [c]); File target = new File (loaderClasspathDir, classNames [c]); if (! target.exists () || (source.lastModified () > target.lastModified ())) { if (target.exists ()) target.delete (); source.renameTo (target); } }
URL [] URLlist = new URL [] {loaderClasspathDir.toURL ()};
// simulate 2 different classloader namespaces: this is namespace #1 URLClassLoader l1 = new URLClassLoader (URLlist); Class c1 = l1.loadClass ("EnumConsumer"); IEnumConsumer obj1 = (IEnumConsumer) c1.newInstance ();
// ... and this is namespace #2: URLClassLoader l2 = new URLClassLoader (URLlist); Class c2 = l2.loadClass ("EnumConsumer"); IEnumConsumer obj2 = (IEnumConsumer) c2.newInstance ();
// get data to pass between obj1 and obj2: Vector objects = obj1.getObjects ();
// this works as expected: obj1.validate (objects);
// this fails: obj2.validate (objects); }


In the above code, I assume that all classes in the project compile into the out directory, which will be in the system classloader's classpath when the program runs. I first execute a loop that moves Enum and EnumConsumer classes in a separate data directory, which classloaders l1 and l2 will use. This simulates l1 and l2 being packaged in the same deployment unit and is necessary to prevent them from delegating to their common parent system classloader. IEnumConsumer is left alone so that the result of ClassLoader.loadClass() could be cast to it. Both l1 and l2 are then asked to create two EnumConsumer instances, obj1 and obj2, which then validate the result of obj1.getObjects() twice:

>java -cp out Main
element 0 Ok
element 1 Ok
element 0 [FALSE] != Enum.FALSE
element 1 [TRUE] != Enum.TRUE


The last two lines of output show that obj2 will never be able to use Enum values that obj1 creates. Effectively, obj1 and obj2 have different views of what Enum class and its values are. Neither the reference comparison (==) nor the equals() method will work.

In fact, fixing the Enum class so it works in this case would require a nontrivial amount of effort. You can override Object.equals() for Enum to use reflection in order to compare class names and m_value values. This will of course eliminate the speed advantage of a typesafe enum type. Besides, it would definitely be too much work for something that has no issues with the old typeunsafe construct in the first place.

Ironically, core JDK classes are in little danger of this happening because they always load precisely once from the same bootstrap classloader. However, we, the developers, can run into this issue with custom-loaded code.

Proceed with caution

The typesafe enum pattern may require too much work to be truly safe in all situations, especially if your runtime involves serialization or a complex classloader structure -- typical elements of Java 2 Platform, Enterprise Edition (J2EE) applications. In certain cases, it won't work at all. As such, the pattern is an unreliable substitute for a basic feature that the Java language lacks: a compiler-supported enum feature. In many cases, you're better off using the good old set-of-static-primitive-values enumeration.

About the author

Vladimir Roubtsov has programmed in a variety of procedural languages for more than 12 years and Java since 1995. Currently, he develops enterprise software as a senior developer for Trilogy in Austin, Texas. In his spare time, Vladimir develops software tools based on Java byte code or source code instrumentation.
  • Print
  • Feedback

Resources