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.

Tavian Barnes
  • 12,477
  • 4
  • 45
  • 118
  • 1
    Also see [*"Cannot convert from List to List>"*](http://stackoverflow.com/q/26766704/2891664) for an extended explanation. (You're trying to convert a `Class` 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. – Radiodef May 07 '15 at 03:09
  • @Radiodef Thanks for that link! Sadly a cast doesn't work for annotation parameters, which is the real life use case that inspired this question. – Tavian Barnes May 07 '15 at 03:15
  • 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. – Radiodef May 07 '15 at 03:18

2 Answers2

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.

Community
  • 1
  • 1
Radiodef
  • 37,180
  • 14
  • 90
  • 125
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.

Elliott Frisch
  • 198,278
  • 20
  • 158
  • 249
  • 2
    But other similar constructs like `Class extends CharSequence> clazz = String.class` work. It also works when I write `class Impl implements Iface { }`, or `Class extends Iface> clazz`. – Tavian Barnes May 07 '15 at 01:30
  • 1
    @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.) – Michael Berry May 07 '15 at 01:38
  • @berry120 Ah okay it makes sense to me now. The raw type `Impl` in `Class` 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! – Tavian Barnes May 07 '15 at 01:49