Java Specialists' Java Training Europehome of the java specialists' newsletter

The Java Specialists' Newsletter
Issue 1412007-03-13 Category: Language Java version: 5

GitHub Subscribe Free RSS Feed

Hacking Enums

by Dr. Heinz M. Kabutz
Abstract:
Enums are implemented as constant flyweights. You cannot construct them. You cannot clone them. You cannot make copies with serialization. But here is a way we can make new ones in Java 5.

Welcome to the 141st edition of The Java(tm) Specialists' Newsletter, written at the Athens airport. Olympic Airlines cancelled my later flight, so I have about 4-5 hours waiting time, unpleasant at any airport! As mentioned in my last newsletter, this week Sun Tech Days is happening in London, so please let me know if you are going.

Last year I had the good fortune of attending more Java conferences than most Java developers do in a lifetime. At JavaPolis 2006, Ted Neward interviewed me. Ted is an excellent interviewer and seems to draw out thoughts. One of the things we talked about was a little hack that made it possible to add new enum instances. Stephan Janssen asked me to expound on this idea, so here is newsletter demonstrating how to do it.

Before showing how it is done, please note that this has zero practical use. We are just having some fun with Java, and perhaps learning something in the process.

I made some comments about Java 6 actually being a type of Java 5.1. This was related to the syntax of the actual language, not the utilities and libraries. There are a lot of new libraries, some of which used to be separate and are now bundled (e.g. JAXB). There is sufficient new material to cover a two day course, but fortunately the language stayed the same.

The trick presented here only works with Java 5. We will have to continue searching to find a way to construct enum instances in Java 6.

NEW: Please see our new "Extreme Java" course, combining concurrency, a little bit of performance and Java 8. Extreme Java - Concurrency & Performance for Java 8.

Hacking Enums

Enums are very well protected flyweight classes. The constructor is private. You cannot construct them with reflection. You cannot clone them. If you serialize and then deserialize them, they return the flyweight.

So where is the chink in the armor that I can put my arrow through? To understand how to construct instances of enums, we need to go back a few generations of Java.

In Java 1.0, there were no inner classes. In Java 1.1, these were added without significantly changing the compiled Java classes. You could access private members from within an inner class, for which the compiler would add "package access" static access methods to the outer class. All nice and tidy. The problem came in with private constructors. These are simply changed to "package access" without as much as a warning. If you can thus slip a class into the same package, you will be able to subclass it without difficulties.

Back to Java 5. Enums allow us to add methods, even abstract ones. We then implement these methods in the actual enum values. Something like this:

public enum Word {
  HELLO {
    public void print() {
      System.out.println("Hello");
    }
  },
  WORLD {
    public void print() {
      System.out.println("World");
    }
  };
  public abstract void print();
}

public class AbstractEnumExample {
  public static void main(String[] args) {
    for (Word e : Word.values()) {
      e.print();
    }
  }
}
  

The compiler now generates anonymous inner classes from each of the enum values. Since these are essentially separate classes, the constructor of the superclass enum Word has become package access. Here is what the generated code for HELLO looks like:

final class Word$1 extends Word {
  Word$1(String s, int i) {
      super(s, i, null);
  }
  public void print() {
      System.out.println("Hello");
  }
}
  

We now use the well known compile and switch trick to sneak in our own enum value.

Step 1: Create a Test.java class

We start by writing a test case that should always fail if ever we were able to create an enum (seeing that we cannot create instances of enums):

public class Test {
  public static void main(String[] args) {
    System.out.println("isEnum() = " + Word.class.isEnum());
    boolean found = false;
    Word test = WordFactory.fetchWord();
    for (Word word : Word.values()) {
      if (word != test) {
        System.out.println("OK, it's not " + word);
      } else {
        System.out.println("Ahh, it's " + word);
        found = true;
        break;
      }
    }
    if (!found) {
      System.out.println("So what is it? " + test);
    }
  }
}
  

We then write a WordFactory that returns word instances. We will change this later to return the hacked enum (and thereby break the test):

public class WordFactory {
  public static Word fetchWord() {
    return Word.HELLO;
  }
}
  

Step 2: Create a Word.java, Cool.java and WordFactory.java in another directory

We create an ordinary Java class Word (not an enum) in another directory (but the same package structure). This would have the same constructor signature as the Word enum, like this:

public class Word {
  public Word(String s, int i) {}
}
  

We then create the COOL enum by simply subclassing Word:

public class Cool extends Word {
  public Cool() {
    super("COOL", 2);
  }
  public void print() {
    System.out.println("Cool!");
  }
}
  

Lastly, we create our own WordFactory that returns our Cool Word:

public class WordFactory {
  public static Word fetchWord() {
    return new Cool();
  }
}    
  

Step 3: Mix in the class files

We now compile our special classes and copy Cool.class and WordFactory.class into the same directory where the other classes are (Test, Word, WordFactory).

When we now run Test (using Java 5, remember, not Java 6), we see the following output:

    isEnum() = true
    OK, it's not HELLO
    OK, it's not WORLD
    So what is it? COOL
  

Not at all what we expected, is it?

Nice to see how Sun cleared up that last loophole in enums in Java 6. We will need to now find a new hole to peep through.

Gotta run, I have a plane to catch (literally :-))

Kind regards

Heinz

Language Articles Related Java Course

Java Master
Java Concurrency
Design Patterns
In-House Courses



© 2010-2014 Heinz Kabutz - All Rights Reserved Sitemap
Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners. JavaSpecialists.eu is not connected to Oracle, Inc. and is not sponsored by Oracle, Inc.
@CORE_THE_BAND #RBBJGR