views:

907

answers:

2

A co-worker ran into an interesting issue today, and while I think the actual, big-picture answer is "the fact that we're having this problem means we're doing something wrong", I figured I'd ask this anyway.

Given the following:

public class CrazyEnumTest {

  public class EnumeratedThing<T extends Enum<T>> {
    public T myValue;

    public EnumeratedThing(T value) {
      myValue = value;
    }
  }

  public static void main (String[] args) {
    String className = args[0];
    String enumValue = args[1];

    Enum<?> value1 = Enum.valueOf(Class.forName(className), enumValue);
    EnumeratedThing<?> thing1 = new EnumeratedThing(value1);
  }
}

I get the following compile error on the call to Enum.valueOf:

Bound mismatch: The generic method valueOf(Class<T>, String) of type Enum<E> is not applicable for the arguments (Class<capture#1-of ?>, String). The inferred type capture#1-of ? is not a valid substitute for the bounded parameter <T extends Enum<T>>

So, my question is: is it possible to, given only the String representation of a enumerated type name as well as the .name() of one of its values, get a reference to the corresponding enumerated type object as an Enum?

+7  A: 

The compile error is telling you that Class<?> is not the same as Class<? extends Enum>. Try:

Enum<?> value1 =
        Enum.valueOf((Class<? extends Enum> Class.forName(className), enumValue);

Note that you'll get an unchecked cast warning, so make sure that className actually does represent an enum. You could check by calling the isEnum() method on the Class object, like this:

Class<?> enumClass = Class.forName(className);
if (enumClass.isEnum()) {
    @SuppressWarnings("unchecked") // we just checked it, so it had better work
    Enum<?> value1 = Enum.valueOf((Class<? extends Enum>) enumClass, enumValue);
    EnumeratedThing<?> thing1 = new EnumeratedThing(value1);
}

Of course, you'll get a raw type warning on "new EnumeratedThing(value1)" anyway.

Michael Myers
Hmm...would've sworn I tried that; I must not have had something quite right in my casts. That works fine though, thanks!
Sbodd
Hmm, I tried to cast to Class<? extends Enum<?>> but it doesn't work. I wouldn't have thought the raw version is the correct one...
thSoft
+1  A: 

Just cast the class you create before you call the method, so that it conforms to the method signature :

Class<?> c = Class.forName(className);
Class<? extends Enum> ce = (Class<? extends Enum>)c;
Enum<?> value1 = Enum.valueOf(ce, enumValue);

Now the class is seen as a subclass of Enum.

Remark :

  • it was anyway the correct class at runtime, but the compiler didn't know
  • casting was invented for that : making a runtime-known type known at compile-time
KLE
or for no reason at all... Enum<?> value1 = Enum.valueOf( ( (Class<? extends Enum>)Class.forName(className) ), enumValue);
instanceofTom
Yes, you can shorten the expression ... but I thought as a post, it would be better to be very readable ;-)In some case, the inline casting gets even messier as you need to cast twice. For example, if you receive a List<I> that you know contains only enum objets, and you want to call a method that has a List<Enum> parameter. If I remember well, simply casting it is not acceptable, I needed to cast it to List<?> then to List<Enum>
KLE
how about `Class<? extends Enum> ce = c.asSubclass(Enum.class);`
newacct