Why is is that the following code does not compile?
interface Iface<T> { }
class Impl<T> implements Iface<T> { }
class TestCase {
static Class<? extends Iface<?>> clazz = Impl.class;
}
The error is
java: incompatible types: java.lang.Class<Impl>
cannot be converted to java.lang.Class<? extends Iface<?>>
but I don't see why the wildcard doesn't capture.
The subtyping relationship here is:
Class<? extends Iface>
╱ ╲
Class<? extends Iface<?>> Class<Impl>
(Which I explained in my answer to 'Cannot convert from List<List>
to List<List<?>>
'.)
So essentially it doesn't compile because it's a sideways conversion.
If it's possible, you can do the casting I described over there:
(Class<? extends Iface<?>>)(Class<? extends Impl>)Impl.class
If you can't do the cast, then you probably just have to deal with a raw bounded Class<? extends Iface>
. It's annoying primarily because of the warnings but it opens up the possibility for an error:
interface Iface<T> {
void accept(T a);
}
class Impl2 implements Iface<String> {
public void accept(String a) { }
}
class TestCase {
static Class<? extends Iface> clazz = Impl2.class;
public static void main(String[] args) throws Exception {
// throws ClassCastException
clazz.newInstance().accept(new Object());
}
}
Unlikely to happen, but it depends on what you're doing I suppose.
I tend to think this is a problem with the Java type system.
Possibly there should be a special rule that a type argument ? extends T<?>
contains a type argument ? extends T
such that e.g. a Class<? extends T>
converts to a Class<? extends T<?>>
. This doesn't make sense from the perspective of the existing way that subtyping is defined (T
is a supertype of T<?>
) but it makes sense from the perspective of type safety.
Or e.g. List.class
should be a Class<List<?>>
instead of a Class<List>
.
Or some other clever thing people smarter than me can think up.
The interesting thing about the ClassCastException
I described above is that it's completely artificial. And in fact, preventing it with the unchecked cast causes a warning.
Just a sign that generics in Java are not done yet, I guess.
Because of type-erasure, when you say Impl.class
you get a Class<Impl>
. That is, you can say
Class<Impl> clazz = Impl.class;
Generics are a compile time type-safety feature.