Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic method with redundant type parameter

Tags:

java

generics

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.

like image 322
Philippe Avatar asked Feb 17 '21 22:02

Philippe


1 Answers

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".

like image 103
Eugene Avatar answered Oct 05 '22 16:10

Eugene