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

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

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.

Upcoming Java Specialist Master Courses: - please click here to sign up.

As from May 2010, we are also offering this course on the island of Crete. We only accept 6 students per class in Crete, due to the size of our conference room. Please book early to avoid disappointment!

San Jose CA, Mar 16-19 2010, $3500
Ottawa, Canada, Mar 22-25 2010, $3500
Oslo, Norway, Apr 13-16 2010, Kr 24500
Montreal, Canada, Apr 20-23 2010, $3500
Toronto, Canada, May 17-20 2010, $3500
Chania, Crete, May 25-28, Jun 29-Jul 2 or Aug 24-27 2010, €2500

In-house courses if these dates or locations do not suit you - click here for more information.

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 nokia.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 nokia.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 (nokia.Test, Word, WordFactory).

When we now run nokia.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 AddThis Social Bookmark Button

Book Review
Concurrency
Exceptions
GUI
Inspirational
Language
Performance
Software Engineering
Tips and Tricks
© 2009 Heinz Kabutz - All Rights Reserved Sitemap seo web design Catch22 Marketing