4

This has to be something small, but I'm not getting it.

Why is this causing an unchecked cast compiler warning, when it has the exact same generic declaration as in Enum's valueOf?

public static final <T extends Enum<T>> Enum toValue(T for_value, String value)  {
   try  {
      return  Enum.<T>valueOf((Class<T>)for_value.getClass(), value);
   }  catch(RuntimeException rx)  {
      throw  new IllegalArgumentException(value);
   }
}

Compiler warning:

R:\jeffy\programming\sandbox\xbnjava\xbn\util\EnumUtil.java:92: 
warning: [unchecked] unchecked cast
         Enum.<T>valueOf((Class<T>)for_value.getClass(), value);
                                                     ^
  required: Class<T>
  found:    Class<CAP#1>
  where T is a type-variable:
    T extends Enum<T> declared in method <T>toValue(T,String,String)
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Enum from capture of ? extends Enum
R:\jeffy\programming\sandbox\xbnjava\xbn\util\EnumUtil.java:98: error: missing return statement
   }
   ^
1 error
1 warning

It also happens in one or both of the generics are removed from the function call, such as

Enum.valueOf(for_value.getClass(), value);

This is the closest question I've found: Enum.valueOf throws a warning for unknown type of class that extends Enum?. This enum's type is known.

Community
  • 1
  • 1
aliteralmind
  • 19,847
  • 17
  • 77
  • 108

3 Answers3

9

You should be calling getDeclaringClass(), which will also fix the generics issues (it is specified to return Class<T>). Calling getClass() on an enum constant with its own methods defined can actually return a different class from the enum type.

Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
  • Excellent! Thank you! Here's a related answer: http://stackoverflow.com/a/5758708/2736496, and the `getDeclaringClass` API: http://docs.oracle.com/javase/7/docs/api/java/lang/Enum.html#getDeclaringClass() – aliteralmind Apr 19 '14 at 16:49
  • This is the answer. : ) – Radiodef Apr 19 '14 at 16:50
  • So `>` is not the same as the class of a *value* of that enum. (I'll say it again, whether it's related or not: Recursive generics are a pain!) Thanks again. – aliteralmind Apr 19 '14 at 16:52
1

It is the getClass invocation that is generating the unchecked warning, not Enum#valueOf. The reason for this is that getClass returns a Class<? extends T> rather than a Class<T>. Remember that for_value could actually be a subclass of T.

However;

Although getClass is declared to return a Class<?> the specification for it makes a special case and you can assign the result of it to a Class<? extends T> without the explicit class:

The actual result type is Class<? extends |X|> ...

That is good but the first problem is it returns erasure of X which means a raw type. So the following assignment requires an unchecked cast:

@SuppressWarnings("unchecked")
Class<? extends E> cls = (Class<? extends E>)for_value.getClass();

It's safe to do because of Enum's language restrictions: it's impossible to make an enum with a declaration that would break this.

Unfortunately because Enum has the recursive generic (E extends Enum<E>) the wildcard is bad so you can't invoke valueOf with it.

As I am editing this, someone has posted the correct answer which is to use Enum#getDeclaringClass.

Radiodef
  • 37,180
  • 14
  • 90
  • 125
  • Yeah I'm fooling around with it and it's failing. It's the way Enum is declared, it's a pain and I forgot about this. `getClass` returns a raw `? extends Enum`. I'm going to edit in a minute. – Radiodef Apr 19 '14 at 16:38
  • Recursive generics are a pain. – aliteralmind Apr 19 '14 at 16:40
1

The JavaDocs for Object.getClass() specify:

The actual result type is Class where |X| is the erasure of the static type of the expression on which getClass is called.

The erasure of the generic type T is Object, so for_value.getClass() has a return type of Class<? extends Object>, not Class<T>. Trying to cast that to a Class<T> is what's giving you the unchecked cast warning.

yshavit
  • 42,327
  • 7
  • 87
  • 124
  • So is there a way to avoiding the unchecked cast? – aliteralmind Apr 19 '14 at 16:39
  • You could pass in a `Class clazz` argument to the method -- that would be the standard way. I _think_ you can just ignore the warning (with `SuppressWarnings("unchecked")`), but I'd have to think a bit about that to make sure. – yshavit Apr 19 '14 at 16:41