推理变量具有不兼容的边界。 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 

不幸的是,当instanceX时,这不会完成,因此可以使用更少的类型信息。

可以想象,语言也可以“改进”以对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> type) { test(type); } static final > void test(List type) { L l1=type.get(0), l2=type.get(1); l2.set(0, l1.get(0)); }