推理变量具有不兼容的边界。 Java8 编译器回归?
Inference variable has incompatible bounds. Java 8 Compiler Regression?
以下程序在 Java 7 和 Java 8 的 Eclipse Mars RC2 中编译:
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") 中发生了很大变化。那么,这是错误还是新语言 "feature"?
编辑:我也已将此作为 JI-9021550 报告给 Oracle,但以防万一这是 Java 8 中的 "feature",我也向 Eclipse 报告了这个问题:
免责声明 - 我对这个主题了解不够,以下是我的非正式推理,试图证明 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>
本质上,这是无解的。例如,如果 X=List<?>
.
则不存在 T
不确定 Java7 如何推断出这种情况。但是 javac8(和 IntelliJ)的行为 "reasonably",我会说。
现在,这个变通方法是如何工作的?
List<?> instance = type.newInstance();
b(instance); // ok!
由于通配符捕获而起作用,它引入了更多类型信息,"narrowing" instance
的类型
instance is List<?> => exist W, where instance is List<W> => T=W
不幸的是,当 instance
为 X
时不会这样做,因此可以使用的类型信息较少。
可以想象,语言也可以是 "improved" 来为 X 进行通配符捕获:
instance is X, X is List<?> => exist W, where instance is List<W>
感谢 我们可以将问题缩小到
<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
的赋值是一个扩展转换,方法调用参数也应该发生这种情况。因此,与 模式的区别在于附加的子类型关系。
请注意,虽然我不确定这个特定案例是否符合 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));
}
感谢 bug report,也感谢 Holger 在您的回答中提供的示例。这些和其他几个最终让我质疑 11 年前在 Eclipse 编译器中所做的一个小改动。重点是:Eclipse 非法扩展了捕获算法以递归地应用于通配符边界。
有一个示例表明这一非法更改使 Eclipse 行为与 javac 完全一致。几代 Eclipse 开发人员比我们在 JLS 中可以清楚地看到的更信任这个旧决定。今天我相信之前的偏差一定是有不同的原因。
今天我鼓起勇气在这方面将 ecj 与 JLS 保持一致,瞧,看起来非常难以破解的 5 个错误基本上已经解决了(加上一些小调整作为补偿)。
因此: 是的,Eclipse 有一个错误,但该错误已在 4.7 里程碑 2 中修复:)
以下是 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,从而使推理失败。
以下程序在 Java 7 和 Java 8 的 Eclipse Mars RC2 中编译:
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") 中发生了很大变化。那么,这是错误还是新语言 "feature"?
编辑:我也已将此作为 JI-9021550 报告给 Oracle,但以防万一这是 Java 8 中的 "feature",我也向 Eclipse 报告了这个问题:
免责声明 - 我对这个主题了解不够,以下是我的非正式推理,试图证明 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>
本质上,这是无解的。例如,如果 X=List<?>
.
T
不确定 Java7 如何推断出这种情况。但是 javac8(和 IntelliJ)的行为 "reasonably",我会说。
现在,这个变通方法是如何工作的?
List<?> instance = type.newInstance();
b(instance); // ok!
由于通配符捕获而起作用,它引入了更多类型信息,"narrowing" instance
instance is List<?> => exist W, where instance is List<W> => T=W
不幸的是,当 instance
为 X
时不会这样做,因此可以使用的类型信息较少。
可以想象,语言也可以是 "improved" 来为 X 进行通配符捕获:
instance is X, X is List<?> => exist W, where instance is List<W>
感谢
<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
的赋值是一个扩展转换,方法调用参数也应该发生这种情况。因此,与
请注意,虽然我不确定这个特定案例是否符合 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));
}
感谢 bug report,也感谢 Holger 在您的回答中提供的示例。这些和其他几个最终让我质疑 11 年前在 Eclipse 编译器中所做的一个小改动。重点是:Eclipse 非法扩展了捕获算法以递归地应用于通配符边界。
有一个示例表明这一非法更改使 Eclipse 行为与 javac 完全一致。几代 Eclipse 开发人员比我们在 JLS 中可以清楚地看到的更信任这个旧决定。今天我相信之前的偏差一定是有不同的原因。
今天我鼓起勇气在这方面将 ecj 与 JLS 保持一致,瞧,看起来非常难以破解的 5 个错误基本上已经解决了(加上一些小调整作为补偿)。
因此: 是的,Eclipse 有一个错误,但该错误已在 4.7 里程碑 2 中修复:)
以下是 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,从而使推理失败。