Oliver Gierke Archive About Tags

Subtleties on java.lang.Enum

12 January 2009

As i twittered a few days ago, I stumbled over a subtle issue in the core Java library. Not as extreme as here but one to raise ones eyebrows, too.

I just wanted to write a small method to access an enum value by its String representation or use a default, if the conversion fails:

public static <T extends Enum<T>> T toEnum(String value, T default) {

  // Checking values omitted
  return (T) Enum.valueOf((Class) defaultValue.getClass(), StringUtils
    .upperCase(value))
}

Of course, I wrote a unittest to verify the behaviour and it worked fine. Nevetheless using this class in my application threw an exception for a different (than the one in the testcase) Enum type, claiming the type handed to the valueOf(…) method was not an Enum?

What? I’ve handed over an Enum as default value and you tell me, it is not of an Enum type. I digged down into the details. Looked like Enums with methods broke the code, Enums without methods worked. Seems like the compiler works with anonymous inner classes when creating byte code. The solution to the problem is using getDeclaringClass() instead, as the JavaDoc reveals.

Although I understand that Enums with methods are implemented with inner classes, I do not understand why these inner classes do not extend Enum<T>. Futhermore I cannot believe that the behaviour of getClass() changes depending on whether you use methods in your Enum or not. Strange thing…

Below you find a small test case to reproduce the issue. Be sure you have JUnit in version 4 or above on the classpath.

public class EnumSample {

  private enum Foo {
    FOO;
  }

  private enum Bar {

    BAR {
      @Override
      public void doSomething() {
        System.out.println("INVOKED!");
      }
    };

    public abstract void doSomething();
  }

  @SuppressWarnings("unchecked")
  private <T extends Enum<T>> T toEnum(String value, T defaultValue) {
    return (T) Enum.valueOf(defaultValue.getClass(), value);
  }

  @Test
  public void useSimpleEnum() {
    toEnum("FOO", Foo.FOO);
  }

  @Test(expected = IllegalArgumentException.class)
  public void useComplexEnum() {
    toEnum("BAR", Bar.BAR);
  }
}
blog comments powered by Disqus
Fork me on GitHub