|
The Java Specialists' Newsletter
Issue 141 2007-03-13
Category:
Language
Java version: 5 Hacking Enumsby Dr. Heinz M. KabutzAbstract:
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:
"This course embodies my Java knowledge and experience gained publishing 180 advanced Java newsletters, teaching hundreds of seminars and writing hundreds of thousands of lines of Java code." Heinz Kabutz, The Java Specialists NewsletterParis, France, Feb 9-12 2010, €2500 - click to sign up. Düsseldorf, Germany (in German), Mar 2-5 2010, €2500 - click to sign up. San Jose CA, Mar 16-19 2010, $3500 - click to sign up. Oslo, Norway, Apr 13-16 2010, Kr 24500 - click to sign up. Chania, Crete, May 25-28 2010, €2500 - click to sign up.
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
|