5

I have problems understanding the differences between the following two method signatures.

abstract class Example {
  void test() {
    Class<? extends Parent<? extends Child>> clazz = null;

    works(clazz);
    error(clazz); // Error
  }

  abstract <T extends Child> Parent<T> works(Class<? extends Parent<? extends T>> clazz);
  abstract <T extends Child> Parent<T> error(Class<? extends Parent<T>>           clazz);

  interface Child {}
  interface Parent<U extends Child> {}
}

Compiling this code gives the following error (Tested with 1.8.0_271, 11.0.9 and 15.0.1).

…/src/main/java/Example.java:6:5
java: method error in class Example cannot be applied to given types;
  required: java.lang.Class<? extends Example.Parent<T>>
  found: java.lang.Class<capture#1 of ? extends Example.Parent<? extends Example.Child>>
  reason: cannot infer type-variable(s) T
    (argument mismatch; java.lang.Class<capture#1 of ? extends Example.Parent<? extends Example.Child>> cannot be converted to java.lang.Class<? extends Example.Parent<T>>)

Why is ? extends needed? T already extends Child in the type parameter (T extends Child) and this additional ? extends seems redundant to me.


Update:

Starting javac with -DverboseResolution=all is a bit more descriptive but still confusing

…src/main/java/Example.java:6: error: method error in class Example cannot be applied to given types;
    error(clazz); // Error
    ^
  required: Class<? extends Parent<T>>
  found: Class<CAP#1>
  reason: cannot infer type-variable(s) T
    (argument mismatch; Class<CAP#1> cannot be converted to Class<? extends Parent<T>>)
  where T is a type-variable:
    T extends Child declared in method <T>error(Class<? extends Parent<T>>)
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Parent<? extends Child> from capture of ? extends Parent<? extends Child>

Interpreting this:

Required is Class<? extends Parent<T>>. T is extends Child. So let's put this together into Class<? extends Parent<? extends Child>>.

Found is Class<CAP#1>. CAP#1 is extends Parent<? extends Child>. Together this gives me Class<? extends Parent<? extends Child>>

So both signatures are the same?!?


Update:

Compiling this code in Eclipse works. In contrast to javac the Eclipse compiler treats the signatures as the same.

Philippe
  • 157
  • 1
  • 11
  • 1
    In order to get an answer to your question, you need to understand what does ` extends T>` means? Here is an SO question that might be helpful: https://stackoverflow.com/q/897935/634958 – S1LENT WARRIOR Feb 17 '21 at 22:42

2 Answers2

4

The really long explanation I did for myself too, is here; if you want to read it. Plus this is related to capture conversion.

In short, the compiler simply can't prove that a wildcard capture is compatible with T, in your case. If you run with: javac --debug=verboseResolution=all ..., you will see an error like:

....
(argument mismatch; Class<CAP#1> cannot be converted to Class<? extends Parent<T>>)

So a capture conversion (CAP#1) can't simply satisfy the compiler here. The way to make it work is to introduce one more capture, via ...<? extends T>. This can be proven by the compiler that ? extends Child is <: (notation explained in the second link) of ? extends T; thus covariance is established.

It is also said that a "bounded wildcard makes the type covariant".

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • Im still confused. I my understanding I already established covariance by writing ``. So `T` already is any subclass of Child. I don't understand why I must do this a second time by writing ` extends T>` – Philippe Feb 18 '21 at 19:29
  • @Philippe this probably has to do with the fact that a wildcard has issues with a type parameter. I am not going to do through the JLS to find this though, because this parts of the `JLS` are the most complicated to read, imho. Sorry, I do not have a better answer for you. – Eugene Feb 19 '21 at 17:24
2

The type Class<Parent<? extends Child>> can be passed to the first signature and cannot be passed to the second, and since Class<Parent<? extends Child>> can be assigned to a Class<? extends Parent<? extends Child>>, it follows that Class<? extends Parent<? extends Child>> also cannot be passed to the second signature.

So the question reduces to why Class<Parent<? extends Child>> cannot be passed to the second signature (<T extends Child> Parent<T> error(Class<? extends Parent<T>> clazz)). We can see that that is because Parent<? extends Child> cannot a subtype of Parent<T>, no matter what specific type you choose for T. (Parent<T> is a subtype of Parent<? extends Child>, but not the other way around.)

Consider the equivalent question of why List<Parent<? extends Child>> cannot be passed to a signature of <T extends Child> Parent<T> error(List<? extends Parent<T>> clazz). A List<Parent<? extends Child>> allows you to add both Parent<ChildA> and Parent<ChildB> elements into the same list at the same time. In other words, it is a "heterogenous list". On the other hand, for any given choice of T, a List<Parent<T>> (or List<SubclassOfParent<T>>) is a list that can only contain Parent objects of a single type argument. It is a "homogenous list". Even if generics allows you to operate on homogenous lists of various type arguments generically, homogenous lists are still different beasts from heterogenous lists. The two are incompatible.

newacct
  • 119,665
  • 29
  • 163
  • 224