java.lang.Class generics and wildcards
Asked Answered
L

2

6

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.

Liatrice answered 7/5, 2015 at 1:9 Comment(3)
Also see "Cannot convert from List<List> to List<List<?>>" for an extended explanation. (You're trying to convert a Class<Impl> to a Class<? extends Iface<?>> so essentially the same rules apply.) If you really need this then it should be fine to cast it in the way I've described over there. (Class<? extends Iface<?>>)(Class<? extends Impl>)Impl.class Otherwise, avoid raw type arguments.Albumose
@Albumose Thanks for that link! Sadly a cast doesn't work for annotation parameters, which is the real life use case that inspired this question.Liatrice
Ouch. I'm not sure what to suggest then. You could change your annotation to Class<? extends Iface> but it's eh. Depends on what you're using the Class for. You could annotate it that way and kludge cast it later.Albumose
A
3

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.

Albumose answered 7/5, 2015 at 4:32 Comment(0)
C
1

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.

Coughlin answered 7/5, 2015 at 1:24 Comment(3)
But other similar constructs like Class<? extends CharSequence> clazz = String.class work. It also works when I write class Impl implements Iface<String> { }, or Class<? extends Iface> clazz.Liatrice
@TavianBarnes The key is that String does not take any generic arguments - as you point out, if Impl doesn't have any generic arguments it also works fine. (Note that both these classes inherit from other classes that do have generic parameters, that is ok - it is if the class has a generic parameter itself that presents the a difficulty to the compiler (because of type erasure.)Lorileelorilyn
@berry120 Ah okay it makes sense to me now. The raw type Impl in Class<Impl> causes all types in the hierarchy of Impl to be erased, so we effectively have Impl extends Iface but not Impl extends Iface<?> for any ?. Thanks!Liatrice

© 2022 - 2024 — McMap. All rights reserved.