Sunday 3 August 2008

Java abstract enumeration and java.lang.Class.isEnum() problem

One of new features in Java 1.5 is direct support of enumeration using enum keyword. According to this the new method isEnum() has been added to java.lang.Class class (which is part of Java Reflections API). The problem is that the isEnum method doesn’t always behave as you would expect.

Let’s start with the following Java code:

public class MainTest {

// Enum with custom constructor
enum EnumPrivate {

VALUE(1);

private int value;

private EnumPrivate(int value) {
this.value = value;
}

public int getValue() {
return value;
}

}

// Enum having an abstract method
enum EnumAbstract {

VALUE(1) {
public int getRandomValue() {
return 10;
}
};

private int value;

private EnumAbstract(int value) {
this.value = value;
}

public abstract int getRandomValue();

}


/**
* @param args
*/
public static void main(String[] args) {
System.out.println(EnumPrivate.VALUE.getClass().isEnum());
System.out.println(EnumAbstract.VALUE.getClass().isEnum());
}

}


Both EnumPrivate and EnumAbstract are not simple enums. The first one has custom constructor (which must be private) and the second an abstract method which must be implemented in each enum’s value. Let’s try to run the main method. You would probably expect “true” twice in the output but you will get the “true” and “false”. It’s quite strange isn’t it?

Let’s try to find out what the java.lang.Class documentation says about isEnum() method. You would find the following:

Returns true if and only if this class was declared as an enum in the source code.

The description of the method doesn’t say anything special about enums with abstract methods so let’s find the isEnum method’s source code in JDK. You could find the following:

public boolean isEnum() {
return (this.getModifiers() & ENUM) != 0 &&
this.getSuperclass() == java.lang.Enum.class;
}


You can see that isEnum returns true if the super class of the class represeting an enum is java.lang.Enum class (we will not bother about modifier) but what's the supeclass of the our enum class? How to find out? The answer is that they are compiled to ordinary Java classes in the same way as any other class so let’s try to decompile them. You would get the result similar to the following:

final class EnumPrivate extends Enum
{

public static final EnumPrivate VALUE;
private int value;
private static final EnumPrivate ENUM_VALUES[];

static
{
VALUE = new EnumPrivate("VALUE", 0, 1);
ENUM_VALUES = (new EnumPrivate[] {
VALUE
});
}

private EnumPrivate(String s, int i, int value)
{
super(s, i);
this.value = value;
}



}

abstract class EnumAbstract extends Enum
{

public static final EnumAbstract VALUE;
private int value;
private static final EnumAbstract ENUM_VALUES[];

static
{
VALUE = new EnumAbstract("VALUE", 0, 1) {

public int getRandomValue()
{
return 10;
}

};
ENUM_VALUES = (new EnumAbstract[] {
VALUE
});
}

private EnumAbstract(String s, int i, int value)
{
super(s, i);
this.value = value;
}

public abstract int getRandomValue();

EnumAbstract(String s, int i, int j, EnumAbstract EnumAbstract)
{
this(s, i, j);
}

….

}


If you compare EnumPrivate and EnumAbstract then you should find the problem. EnumPrivate extends the Enum and it’s VALUE is EnumPrivate. EnumAbstract extends Enum as well but because it has the abstract method it must be the abstract class. VALUE is then anonymous inner child class of the EnumAbstract so its superclass is not Enum but EnumAbstract!

As you can see enums (or isEnum) should be corrected a bit in the future versions of Java. You have got two enumerations according to specification but only one of them is correctly recognized as enumeration.

You can find many issues when using enums with abstract methods in combination with third party libraries (or applications). Assume a library for automatic converting standard Java types to e.g. String. The library will get the Object and then need to find out the type of object to correctly do the conversion to String. If the library relies on the isEnum() method to recognize enums then it won’t correctly recognize your enum with abstract method value as enum. This happened to me and I spend a few hours searching for the problem and that’s the reason why I decided to share my experience with you.