Brett Ryan
Brett Ryan

Reputation: 28255

Why do I need to explicitly cast a generic call?

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)

Upvotes: 13

Views: 831

Answers (4)

Jeremy Roman
Jeremy Roman

Reputation: 16345

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>();
}

Upvotes: 4

Affe
Affe

Reputation: 47954

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.

Upvotes: 7

Arsen Alexanyan
Arsen Alexanyan

Reputation: 3141

It's because n.getClass() always returns Class<?> instead of Class<T> therefore compiler cannot directly resolve this. You always need here to cast explicitly. For example

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

Upvotes: 3

Jakub Zaverka
Jakub Zaverka

Reputation: 8874

Generics and their respective final types are determined at compile time, not at runtime. At runtime, all generic information is erased - this is known as Type erasure. It seems that here you are trying to determine the generic type at the time of the function call, which is not supported in Java

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

Here, you need to return List<T> - an instance of list. A concrete instance must have a concrete parameter T. I.e. one cannot return List<T extends Widget> - the type would be unknown. What would you store in such a list?

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

Here you want to return a list based on the supplied class - but see that you actually are creating a generic ArrayList which then is casted to the required type. The problem is that T here is not the same as T in the previous method. You would need to declare the whole class generic with one T, to make them the same.

Upvotes: 1

Related Questions