以下程序在Java 7和Eclipse Mars RC2 for Java 8中编译:
import java.util.List;
public class Test {
static final void a(Class<? extends List<?>> type) {
b(newList(type));
}
static final <T> List<T> b(List<T> list) {
return list;
}
static final <L extends List<?>> L newList(Class<L> type) {
try {
return type.newInstance();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
使用javac 1.8.0_45编译器报告以下编译错误:
Test.java:6: error: method b in class Test cannot be applied to given types;
b(newList(type));
^
required: List<T>
found: CAP#1
reason: inference variable L has incompatible bounds
equality constraints: CAP#2
upper bounds: List<CAP#3>,List<?>
where T,L are type-variables:
T extends Object declared in method <T>b(List<T>)
L extends List<?> declared in method <L>newList(Class<L>)
where CAP#1,CAP#2,CAP#3 are fresh type-variables:
CAP#1 extends List<?> from capture of ? extends List<?>
CAP#2 extends List<?> from capture of ? extends List<?>
CAP#3 extends Object from capture of ?
解决方法是在本地分配变量:
import java.util.List;
public class Test {
static final void a(Class<? extends List<?>> type) {
// Workaround here
List<?> variable = newList(type);
b(variable);
}
static final <T> List<T> b(List<T> list) {
return list;
}
static final <L extends List<?>> L newList(Class<L> type) {
try {
return type.newInstance();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
我知道Java 8中的类型推断发生了很大的变化(e.g. due to JEP 101 "generalized target-type inference")。那么,这是一个错误还是一种新语言&#34;功能&#34;?
编辑:我还向甲骨文报告了这个问题,作为JI-9021550,但以防这是一个&#34;功能&#34;在Java 8中,我也向Eclipse报告了这个问题:
答案 0 :(得分:7)
免责声明 - 我对这个主题不够了解,以下是我的非正式推理,试图证明javac的行为是合理的。
我们可以将问题减少到
<X extends List<?>> void a(Class<X> type) throws Exception
{
X instance = type.newInstance();
b(instance); // error
}
<T> List<T> b(List<T> list) { ... }
要推断T
,我们有约束
X <: List<?>
X <: List<T>
基本上,这是无法解决的。例如,如果T
,则不存在X=List<?>
。
不确定Java7如何推断这种情况。但是javac8(和IntelliJ)表现得很合理,我会说。
现在,这个解决方法如何运作?
List<?> instance = type.newInstance();
b(instance); // ok!
它的工作原因是通配符捕获,它引入了更多的类型信息,&#34;缩小&#34; instance
instance is List<?> => exist W, where instance is List<W> => T=W
不幸的是,instance
为X
时未执行此操作,因此可以使用较少的类型信息。
可以想象,语言可以得到改善&#34;也为X做通配符捕获:
instance is X, X is List<?> => exist W, where instance is List<W>
答案 1 :(得分:6)
感谢bug report,感谢Holger,感谢你的回答。这些和其他几个人最后让我质疑11年前在Eclipse编译器中做出的一个小改动。关键是:Eclipse非法扩展了捕获算法,以递归方式应用于通配符边界。
有一个例子,这个非法的变化完全符合Eclipse与javac的行为。与JLS中我们能够清楚看到的一样,Eclipse开发人员已经相信这个旧决定。今天我认为以前的偏差必然有不同的原因。
今天我鼓起勇气让ecj与JLS在这方面保持一致,并且5个看起来非常难以破解的错误,基本上已经解决了这个问题(加上一点点调整以及补偿)。
Ergo:是的,Eclipse有一个bug,但是从4.7里程碑2开始修复了这个bug:)
以下是ecj今后将报告的内容:
The method b(List<T>) in the type Test is not applicable for the arguments (capture#1-of ? extends List<?>)
捕获范围内的通配符没有找到检测兼容性的规则。更确切地说,在推理过程中的某些时间(精确地合并)我们遇到以下约束(T#0表示推理变量):
⟨T#0 = ?⟩
天真地,我们可以将类型变量解析为通配符,但是 - 可能是因为通配符不被视为类型 - 减少规则将上述定义为减少为FALSE,从而使推理失败。
答案 2 :(得分:5)
感谢bayou.io’s answer我们可以将问题缩小到
这一事实<X extends List<?>> void a(X instance) {
b(instance); // error
}
static final <T> List<T> b(List<T> list) {
return list;
}
时出现错误
<X extends List<?>> void a(X instance) {
List<?> instance2=instance;
b(instance2);
}
static final <T> List<T> b(List<T> list) {
return list;
}
可以毫无问题地编译。 instance2=instance
的赋值是一种扩展转换,对于方法调用参数也应该发生。因此this answer模式的差异是附加的子类型关系。
请注意,虽然我不确定这个特定情况是否与Java语言规范一致,但是一些测试显示Eclipse接受代码的原因可能是它通常对于泛型类型更为草率,以下,绝对不正确的代码可以编译,没有任何错误或警告:
public static void main(String... arg) {
List<Integer> l1=Arrays.asList(0, 1, 2);
List<String> l2=Arrays.asList("0", "1", "2");
a(Arrays.asList(l1, l2));
}
static final void a(List<? extends List<?>> type) {
test(type);
}
static final <Y,L extends List<Y>> void test(List<L> type) {
L l1=type.get(0), l2=type.get(1);
l2.set(0, l1.get(0));
}