这段代码在Eclipse中编译,但在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输出:
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
----------------------------------------------------------------
哪个编译器错了,为什么? (Eclipse bug report and parts of discussion here)
答案 0 :(得分:10)
此代码与JLS 8合法.javac版本8及更早版本在如何处理通配符和捕获方面存在一些错误。从版本9(早期访问,我尝试版本ea-113和更新版本)开始,javac也接受此代码。
要了解编译器如何根据JLS对此进行分析,必须分清哪些是通配符捕获,类型变量,推理变量等。
c
的类型为Consumer<capture#1-of ?>
(javac会写Consumer<CAP#1>
)。这种类型未知,但已修复。
m2
的参数类型为Consumer<? super T>
,其中T
是要通过类型推断实例化的类型变量。
在类型推断期间,推理变量(由ecj表示为T#0
)用于表示T
。
类型推断在于确定T#0
是否可以在不违反任何给定类型约束的情况下实例化为任何类型。在这种特殊情况下,我们从这个约束开始:
⟨c→消费者&lt;?超级T#0&gt;⟩
逐步减少(通过应用JLS 18.2):
⟨Consumer&lt; capture#1-of?&gt; →消费者&lt;?超级T#0&gt;⟩
⟨capture#1-of? &lt; =?超级T#0⟩
⟨T#0&lt ;:捕获#1-of?⟩
T#0&lt ;: capture#1-of?
最后一行是“类型绑定”并完成缩减。由于不涉及进一步的约束,因此解决方案将T#0
简单地实例化为类型capture#1-of ?
。
通过这些步骤,类型推断已证明m2
适用于此特定调用。 QED。
直观地说,所示解决方案告诉我们:无论捕获可能表示何种类型,如果T
设置为表示完全相同的类型,则不会违反任何类型约束。这是可能的,因为在开始类型推断之前,捕获是固定的。
答案 1 :(得分:6)
请注意,可以毫无问题地编译以下内容:
public class Test {
public static final void m1(Consumer<?> c) {
m2(c);
}
private static final <T> void m2(Consumer<T> c) {
}
}
尽管我们不知道消费者的实际类型,但我们知道它可以分配给Consumer<T>
,尽管我们不知道T
是什么(不知道是什么{{1}无论如何,这是通用代码中的标准。)
但如果T
的作业有效,则Consumer<T>
的作业也是如此。我们甚至可以通过一个中间步骤来实现这一点:
Consumer<? super T>
没有编译器对象。
当您使用命名类型替换通配符时也会被接受,例如
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) {
}
}
此处,public class Test {
public static final void m1(Consumer<?> c) {
m2(c);
}
private static final <E,T extends E> void m2(Consumer<E> c) {
}
}
是E
的超级类型,就像T
一样。
我试图找到最接近这种情况的? super T
的错误报告,但是当谈到javac
和通配符类型时,它们有很多,我最终放弃了。免责声明:这并不意味着存在如此多的错误,只是报告了很多相关的情况,这可能都是同一个错误的不同症状。
唯一重要的是,它已经在Java 9中得到修复。