为什么这个Java 8程序无法编译?

时间:2014-04-14 15:00:19

标签: java generics type-inference 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之间的向后源不兼容。我已经通过Incompatibilities between Java SE 8 and Java SE 7列表,但没有发现任何适合我的问题。

那么,这是一个错误吗?

环境:

$ /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)

3 个答案:

答案 0 :(得分:40)

Java语言规范在类型推断方面发生了重大变化。在JLS7中,§15.12.2.7§15.12.2.8中描述了类型推断,而在JLS8中,有一整章专门用于Chapter 18. Type Inference

规则非常复杂,包括JLS7和JLS8。很难区分差异,但显然存在差异,如§18.5.2部分所示:

  

此推理策略与Java语言规范[...]的Java SE 7版本不同。

然而,改变的目的是向后兼容。请参阅§18.5.2部分的最后一段:

  

[..]该策略允许在典型用例中获得合理的结果,并且向后兼容Java SE 7 Java版Java语言规范中的算法。

我不知道这是不是真的。但是,您的代码有一些有趣的变体,但没有显示问题。例如,以下语句编译时没有错误:

new Acceptor<>(new Impl());

在这种情况下,没有目标类型。这意味着类实例创建表达式不是多表达式类型推断的规则更简单。见§18.5.2

  

如果调用不是多面体表达式,则让绑定集B 3 与B 2 相同。

这也是为什么以下陈述有效的原因。

Acceptor<?> acceptor = (Acceptor<?>) new Acceptor<>(new Impl());

虽然表达式的上下文中有一个类型,但它不算作目标类型。如果赋值表达式调用表达式中未发生类实例创建表达式,则它不能是多元表达。见§15.9

  

如果类实例创建表达式使用菱形表单作为类的类型参数,则它是一个多表达式(第15.2节),它出现在赋值上下文或调用上下文中(第5.2节,第5.3节)。否则,它是一个独立的表达式。

回到你的陈述。 JLS8的相关部分又是§18.5.2。但是,根据JLS8,我不能告诉您以下语句是否正确,如果编译器是正确的错误消息。但至少,你有一些替代方案和指示,以获取更多信息。

Acceptor<?> acceptor = new Acceptor<>(new Impl());

答案 1 :(得分:20)

感谢您的报告。这看起来像一个bug。我会处理它,并且一旦我们获得有关为什么会发生这种情况的更多信息,可能会添加更好的答案。我已提交此错误条目JDK-8043926,以便跟踪它。

答案 2 :(得分:7)

在Java 8中更改了类型推断。现在,对于构造函数和方法,类型推断同时查看目标类型和参数类型。请考虑以下事项:

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使类型推理系统更加智能化。这会导致不兼容吗?一般来说,没有。由于类型擦除,只要程序编译,实际上并不重要的是推断出什么类型。 Java 8是否编译所有Java 7程序?它应该,但是你提出了一个不存在的情况。

似乎正在发生的事情是Java 8没有很好地处理通配符。它似乎将它们视为一种它无法满足的限制性约束,而不是将它们视为缺乏约束。我不确定它是否跟随JLS的来信,但我至少在精神上称这是一个错误。

仅供参考,这确实有效(请注意我的Acceptor没有您的类型限制):

Acceptor<?> a = new Acceptor<>(new Impl2());

请注意,您的示例在方法参数之外使用通配符类型(这是不可取的),我想知道在方法调用中使用菱形运算符的更典型代码中是否会出现相同的问题。 (可能)