Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I need to explicitly cast a generic call?

Tags:

java

generics

Let's suppose I have the following:

public <T extends Widget> List<T> first(T n) {
    return first(n.getClass());
}
public <T extends Widget> List<T> first(Class<T> n) {
    return new ArrayList<>();
}

The compiler complains at line 3 with "incompatible types; required: java.util.List<T>; found: java.util.List<capture#1 of ? extends my.app.Widget>". Which I don't understand why. It seems reasonable to me that the type T could never change in either case other than a sub-type.

This can be fixed via explicit casting, though I do not know why it is required.

public <T extends Widget> List<T> first(T n) {
    return (List<T>)first(n.getClass());
}
public <T extends Widget> List<T> first(Class<T> n) {
    return new ArrayList<>();
}

Could this be a compiler bug?

Note I am using JDK 1.7.0_15:

java version "1.7.0_15"
Java(TM) SE Runtime Environment (build 1.7.0_15-b03)
Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode)
like image 437
Brett Ryan Avatar asked Mar 07 '13 07:03

Brett Ryan


2 Answers

For exactly the reason you stated, the type parameter could actually be a supertype of the real runtime type of the object you passed in! T#getClass() does not return Class<T>, it returns Class<? extends T>

Number number = Integer.valueOf(1);
List<Number> list = first(number);

When you call n.getClass() at runtime is going to return Integer.class, not Number.class, yet you are trying to assign the result to List<Number>! The compiler has no way to know what the real runtime type will be, it only knows at most that List<? extends Number> comes back. Forcing you to put in the cast is its way of saying "I cannot vouch for the safety of this operation, it's on your word that it's correct."

Any time it is impossible for the compiler to confirm that an operation is typesafe, it will force you to cast and thus incur an "unchecked" warning so that it has done its job of letting you know about the problem.

like image 195
Affe Avatar answered Sep 20 '22 16:09

Affe


getClass() returns a value of type Class<?>. When you pass it to the other override of first, you are performing an unchecked cast to Class<? extends Widget>. It's unchecked because generics use "type erasure" at runtime, meaning that the JVM cannot check it then; pass -Xlint:unchecked to see this warning.

Note that this is not Class<T extends Widget>, meaning that the type system does not ensure that the type parameter of this method (the first override of first) is the same as that of the one being called (the other first).

So the result you get back is of type List<? extends Widget> (where ? extends Widget is capture#1), which is not compatible with List<T extends Widget>, and so the compiler correctly produces an error.

In this case, you happen to know (though the compiler does not) that this is a reasonable thing to do, so you can override it with an explicit cast. But in that case, why not just make the method this:

public <T extends Widget> List<T> first() {
    return new ArrayList<T>();
}
like image 37
Jeremy Roman Avatar answered Sep 17 '22 16:09

Jeremy Roman