This piece of code compiles in Eclipse but not in javac:
import java.util.function.Consumer;
public class Test {
public static final void m1(Consumer<?> c) {
m2(c);
}
private static final <T> void m2(Consumer<? super T> c) {
}
}
javac output:
C:\Users\lukas\workspace>javac -version
javac 1.8.0_92
C:\Users\lukas\workspace>javac Test.java
Test.java:5: error: method m2 in class Test cannot be applied to given types;
m2(c);
^
required: Consumer<? super T>
found: Consumer<CAP#1>
reason: cannot infer type-variable(s) T
(argument mismatch; Consumer<CAP#1> cannot be converted to Consumer<? super T>)
where T is a type-variable:
T extends Object declared in method <T>m2(Consumer<? super T>)
where CAP#1 is a fresh type-variable:
CAP#1 extends Object from capture of ?
1 error
----------------------------------------------------------------
Which compiler is wrong and why? (Eclipse bug report and parts of discussion here)
A lower bounded wildcard is expressed using the wildcard character ('? '), following by the super keyword, followed by its lower bound: <? super A>. Note: You can specify an upper bound for a wildcard, or you can specify a lower bound, but you cannot specify both.
The only restriction on wilds cards is that you cannot it as a type argument of a generic method while invoking it.
A bounded wildcard is one with either an upper or a lower inheritance constraint. The bound of a wildcard can be either a class type, interface type, array type, or type variable. Upper bounds are expressed using the extends keyword and lower bounds using the super keyword.
both bounded and unbounded wildcards provide a lot of flexibility on API design especially because Generics is not covariant and List<String> can not be used in place of List<Object>. Bounded wildcards allow you to write methods that can operate on Collection of Type as well as Collection of Type subclasses.
This code is legal wrt JLS 8. javac version 8 and earlier had several bugs in how they handle wildcards and captures. Starting with version 9 (early access, I tried version ea-113 and newer) also javac accepts this code.
To understand how a compiler analyzes this according to JLS, it is essential to tell apart what are wildcard captures, type variables, inference variables and such.
The type of c
is Consumer<capture#1-of ?>
(javac would write Consumer<CAP#1>
). This type is unknown, but fixed.
The parameter of m2
has type Consumer<? super T>
, where T
is a type variable to be instantiated by type inference.
During type inference an inference variable, denoted by ecj as T#0
, is used to represent T
.
Type inference consists in determining, whether T#0
can be instantiated to any type without violating any given type constraints. In this particular case we start with this contraint:
⟨c → Consumer<? super T#0>⟩
Which is stepwise reduced (by applying JLS 18.2):
⟨Consumer<capture#1-of ?> → Consumer<? super T#0>⟩
⟨capture#1-of ? <= ? super T#0⟩
⟨T#0 <: capture#1-of ?⟩
T#0 <: capture#1-of ?
The last line is a "type bound" and reduction is done. Since no further constraints are involved, resolution trivially instantiates T#0
to the type capture#1-of ?
.
By these steps, type inference has proven that m2
is applicable for this particular invocation. qed.
Intuitively, the shown solution tells us: whatever type the capture may represent, if T
is set to represent the exact same type, no type constraints are violated. This is possible, because the capture is fixed before starting type inference.
Note that the following can be compiled without problems:
public class Test {
public static final void m1(Consumer<?> c) {
m2(c);
}
private static final <T> void m2(Consumer<T> c) {
}
}
Despite we don’t know the actual type of the consumer, we know that it will be assignable to Consumer<T>
, though we don’t know what T
is (not knowing what T
is, is the norm in generic code anyway).
But if the assignment to Consumer<T>
is valid, the assignment to Consumer<? super T>
would be as well. We can even show this practically with an intermediate step:
public class Test {
public static final void m1(Consumer<?> c) {
m2(c);
}
private static final <T> void m2(Consumer<T> c) {
m3(c);
}
private static final <T> void m3(Consumer<? super T> c) {
}
}
No compiler objects that.
It will also be accepted when you replace the wild card with a named type, e.g.
public class Test {
public static final void m1(Consumer<?> c) {
m2(c);
}
private static final <E,T extends E> void m2(Consumer<E> c) {
}
}
Here, E
is a super type of T
, just like ? super T
is.
I tried to find the bug report for javac
closest to this scenario, but when it comes to javac
and wildcard types, there are so many of them, that I eventually gave up. Disclaimer: this does not imply that there are so many bugs, just that there are so many related scenarios reported, which may all be different symptoms of the same bug.
The only thing that matters, is, that it is already fixed in Java 9.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With