Reputation: 221115
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)
Upvotes: 17
Views: 367
Reputation: 298459
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.
Upvotes: 6
Reputation: 8178
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.
Upvotes: 12