为什么这个Java 8程序不能编译?
这个程序在Java 7(或者在带有-source 7
Java 8中)编译得很好,但是没有用Java 8编译:
interface Iface<T> {} class Impl implements Iface<Impl> {} class Acceptor<T extends Iface<T>> { public Acceptor(T obj) {} } public class Main { public static void main(String[] args) { Acceptor<?> acceptor = new Acceptor<>(new Impl()); } }
结果:
Main.java:10: error: incompatible types: cannot infer type arguments for Acceptor<> Acceptor<?> acceptor = new Acceptor<>(new Impl()); ^ reason: inference variable T has incompatible bounds equality constraints: Impl upper bounds: Iface<CAP#1>,Iface<T> where T is a type-variable: T extends Iface<T> declared in class Acceptor where CAP#1 is a fresh type-variable: CAP#1 extends Iface<CAP#1> from capture of ? 1 error
换句话说,这是Java 7和8之间的后端源不兼容。我已经通过Java SE 8和Java SE 7列表之间的不兼容性,但没有find任何适合我的问题的东西。
那么,这是一个错误?
环境:
$ /usr/lib/jvm/java-8-oracle/bin/java -version java version "1.8.0" Java(TM) SE Runtime Environment (build 1.8.0-b132) Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)
感谢您的报告。 这看起来像一个错误。 我会照顾它,并可能添加更好的答案,一旦我们有更多的信息,为什么发生这种情况。 我已经提交了这个bug条目JDK-8043926 ,来跟踪它。
对于types推断 ,Java语言规范发生了很大的变化。 在JLS7中, types推断在§15.12.2.7和§15.12.2.8中描述,而在JLS8中则有一章专用于第18章。types推断 。
规则相当复杂,无论是在JLS7还是JLS8中。 很难区分这些差异,但很明显存在差异,从§18.5.2节可以明显看出:
这种推理策略不同于Java语言规范[…]的Java SE 7版本。
但是,改变的意图是向后兼容的。 请参阅第18.5.2节的最后一段:
该策略允许在典型用例中获得合理的结果,并向后兼容Java语言规范的Java SE 7版中的algorithm。
我不知道这是否是真实的。 但是,您的代码有一些有趣的变化,不会显示问题。 例如,下面的语句编译没有错误:
new Acceptor<>(new Impl());
在这种情况下,没有目标types 。 这意味着类实例创buildexpression式不是一个多expression式 , types推断的规则更简单。 见§18.5.2 :
如果调用不是一个polyexpression式,那么让边界集合B 3与B 2相同。
这也是为什么下面的声明起作用的原因。
Acceptor<?> acceptor = (Acceptor<?>) new Acceptor<>(new Impl());
虽然在expression式的上下文中有一个types,但它不会被视为目标types 。 如果类实例创buildexpression式在赋值expression式或调用 expression式中不发生,那么它不能是一个多expression式 。 见§15.9 :
类实例创buildexpression式是一个多expression式(§15.2),如果它使用钻石forms作为类的types参数,并且出现在赋值上下文或调用上下文(§5.2,§5.3)中。 否则,这是一个独立的expression。
回到你的发言。 JLS8的相关部分也是§18.5.2 。 但是,根据JLS8,如果编译器与错误消息一致,我不能告诉你以下语句是否正确。 但至less,你有一些select和指针的进一步信息。
Acceptor<?> acceptor = new Acceptor<>(new Impl());
types推断在Java 8中已经改变了。现在,types推断既查看目标types,也查看参数types,对于构造函数和方法都是如此。 考虑以下:
interface Iface {} class Impl implements Iface {} class Impl2 extends Impl {} class Acceptor<T> { public Acceptor(T obj) {} } <T> T foo(T a) { return a; }
下面现在可以在Java 8中使用(但不是在Java 7中):
Acceptor<Impl> a = new Acceptor<>(new Impl2()); // Java 8 cleverly infers Acceptor<Impl> // While Java 7 infers Acceptor<Impl2> (causing an error)
这当然会在两个方面给出一个错误:
Acceptor<Impl> a = new Acceptor<Impl2>(new Impl2());
在Java 8中这也是可以的:
Acceptor<Impl> a = foo (new Acceptor<>(new Impl2())); // Java 8 infers Acceptor<Impl> even in this case // While Java 7, again, infers Acceptor<Impl2> // and gives: incompatible types: Acceptor<Impl2> cannot be converted to Acceptor<Impl>
以下给出了一个错误,但错误是不同的:
Acceptor<Impl> a = foo (new Acceptor<Impl2>(new Impl2())); // Java 7: // incompatible types: Acceptor<Impl2> cannot be converted to Acceptor<Impl> // Java 8: // incompatible types: inferred type does not conform to upper bound(s) // inferred: Acceptor<Impl2> // upper bound(s): Acceptor<Impl>,java.lang.Object
显然,Java 8使得types推断系统更加智能。 这是否会导致不兼容? 一般来说,不。 由于types擦除,只要程序编译,它实际上并不关心什么types被推断。 Java 8是否可以编译所有Java 7程序? 它应该,但是你提出了一个没有的情况。
似乎正在发生的事情是,Java 8不能很好地处理通配符。 而不是把它们视为一种约束的缺失,似乎把它们视为一种不能满足的约束性约束。 我不确定它是否跟随JLS的信,但至less在精神上,我会把它称为一个错误。
仅供参考,这是行不通的(请注意,我的Acceptor
没有你的types限制):
Acceptor<?> a = new Acceptor<>(new Impl2());
请注意,您的示例在方法参数外(这是不可取的)使用通配符types,不知道在方法调用中使用菱形运算符的更典型的代码中是否会出现相同的问题。 (大概。)