Running on Java 22-ea+27-2262 (Preview)
Home of The JavaSpecialists' Newsletter

141Hacking Enums

Author: Dr. Heinz M. KabutzDate: 2007-03-13Java Version: 5Category: Language
 

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.

javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.

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

 

Comments

We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)

When you load these comments, you'll be connected to Disqus. Privacy Statement.

Related Articles

Browse the Newsletter Archive

About the Author

Heinz Kabutz Java Conference Speaker

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Superpack '23

Superpack '23 Our entire Java Specialists Training in one huge bundle more...

Free Java Book

Dynamic Proxies in Java Book
Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

We can help make your Java application run faster and trouble-shoot concurrency and performance bugs...