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