推理变量具有不兼容的界限。Java 8 编译器回归?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/30622759/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Inference variable has incompatible bounds. Java 8 Compiler Regression?
提问by Lukas Eder
The following program compiles in Java 7 and in Eclipse Mars RC2 for Java 8:
以下程序在 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);
}
}
}
Using the javac 1.8.0_45 compiler, the following compilation error is reported:
使用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 ?
A workaround is to locally assign a variable:
一种解决方法是在本地分配一个变量:
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);
}
}
}
I know that type inference has changed a lot in Java 8 (e.g. due to JEP 101 "generalized target-type inference"). So, is this a bug or a new language "feature"?
我知道 Java 8 中的类型推断发生了很大变化(例如,由于 JEP 101“通用目标类型推断”)。那么,这是一个错误还是一个新的语言“功能”?
EDIT: I have also reported this to Oracle as JI-9021550, but just in case this is a "feature" in Java 8, I've reported the issue also to Eclipse:
编辑:我也向 Oracle 报告了 JI-9021550,但以防万一这是 Java 8 中的一个“特性”,我也向 Eclipse 报告了这个问题:
采纳答案by Stephan Herrmann
Thanks for the bug report, and thanks, Holger, for the example in your answer. These and several others finally made me question one small change made in the Eclipse compiler 11 years ago. The point was: Eclipse had illegally extended the capture algorithm to apply recursively to wildcard bounds.
感谢您的错误报告,并感谢 Holger,为您的答案中的示例。这些和其他几个最终让我质疑 11 年前在 Eclipse 编译器中所做的一个小变化。关键是:Eclipse 非法扩展了捕获算法以递归地应用于通配符边界。
There was one example where this illegal change perfectly aligned Eclipse behavior with javac. Generations of Eclipse developers have trusted this old decision more than what we could clearly see in JLS. Today I believe that previous deviation must have had a different reason.
有一个例子,这种非法更改将 Eclipse 行为与 javac 完美对齐。与我们在 JLS 中清楚地看到的相比,几代 Eclipse 开发人员更信任这个旧决定。今天我相信之前的偏差一定是有不同的原因。
Today I took the courage to align ecj with JLS in this regard and voila 5 bugs that appeared to be extremely hard to crack, have essentially been solved just like that (plus a little tweak here and there as compensation).
今天,我鼓起勇气在这方面将 ecj 与 JLS 保持一致,瞧 5 个看起来极其难以破解的错误,基本上就这样解决了(加上这里和那里的一些小调整作为补偿)。
Ergo:Yes, Eclipse had a bug, but that bug has been fixed as of 4.7 milestone 2 :)
Ergo:是的,Eclipse 有一个错误,但该错误已在 4.7 里程碑 2 中修复:)
Here's what ecj will report henceforth:
以下是 ecj 今后将报告的内容:
The method b(List<T>) in the type Test is not applicable for the arguments (capture#1-of ? extends List<?>)
It's the wildcard inside a capture bound that doesn't find a rule to detect compatibility. More precisely, some time during inference (incorporation to be precise) we encounter the following constraint (T#0 representing an inference variable):
它是捕获边界内的通配符,它没有找到检测兼容性的规则。更准确地说,在推理过程中的某个时间(准确地说是合并),我们遇到了以下约束(T#0 表示推理变量):
?T#0 = ??
Naively, we could just resolve the type variable to the wildcard, but -- presumably because wildcards are not considered types -- the reduction rules define the above as reducing to FALSE, thus letting inference fail.
天真地,我们可以将类型变量解析为通配符,但是——大概是因为通配符不被视为类型——减少规则将上述定义为减少到 FALSE,从而使推理失败。
回答by ZhongYu
Disclaimer - I don't know enough about the subject, and the following is an informal reasoning of mine to try to justify javac's behavior.
免责声明 - 我对这个主题的了解不够,以下是我试图证明 javac 行为合理的非正式推理。
We can reduce the problem to
我们可以将问题简化为
<X extends List<?>> void a(Class<X> type) throws Exception
{
X instance = type.newInstance();
b(instance); // error
}
<T> List<T> b(List<T> list) { ... }
To infer T
, we have constraints
推断T
,我们有约束
X <: List<?>
X <: List<T>
Essentially, this is unsolvable. For example, no T
exists if X=List<?>
.
本质上,这是无法解决的。例如,T
如果不存在X=List<?>
。
Not sure how Java7 infers this case. But javac8 (and IntelliJ) behaves "reasonably", I'd say.
不确定 Java7 如何推断这种情况。但是javac8(和IntelliJ)的行为“合理”,我想说。
Now, how come this workaround works?
现在,这个变通方法是如何工作的?
List<?> instance = type.newInstance();
b(instance); // ok!
It works due to wildcard capture, which introduces more type info, "narrowing" the type of instance
它的工作原理是通配符捕获,它引入了更多类型信息,“缩小”了 instance
instance is List<?> => exist W, where instance is List<W> => T=W
Unfortunately, this is not done when instance
is X
, thus there is less type info to work with.
不幸的是,不这样做instance
的X
,所以有较少的类型信息与工作。
Conceivably, the language could be "improved" to do wildcard capture for X too:
可以想象,该语言也可以“改进”以对 X 进行通配符捕获:
instance is X, X is List<?> => exist W, where instance is List<W>
回答by Holger
Thanks to bayou.io's answerwe can narrow the problem to the fact that
感谢bayou.io 的回答,我们可以将问题缩小到以下事实
<X extends List<?>> void a(X instance) {
b(instance); // error
}
static final <T> List<T> b(List<T> list) {
return list;
}
produces an error while
产生错误,而
<X extends List<?>> void a(X instance) {
List<?> instance2=instance;
b(instance2);
}
static final <T> List<T> b(List<T> list) {
return list;
}
can be compiled without problems. The assignment of instance2=instance
is a widening conversion which should also happen for method invocation arguments. So the difference to the pattern of this answeris the additional subtype relationship.
可以毫无问题地编译。的赋值instance2=instance
是一个扩大转换,它也应该发生在方法调用参数上。所以这个答案的模式的不同之处在于附加的子类型关系。
Note that while I'm not sure whether this specific case is in line with the Java Language Specification, some tests revealed that Eclipse accepting the code is likely due to the fact that it is more sloppy regarding Generic types in general, as the following, definitely incorrect, code could be compiled without any error nor warning:
请注意,虽然我不确定这个特定情况是否符合 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));
}