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

ghi*_*hik 77 java generics type-inference java-8

这个程序在Java 7(或Java 8中-source 7)编译得很好,但是无法用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());
    }
}
Run Code Online (Sandbox Code Playgroud)

结果:

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
Run Code Online (Sandbox Code Playgroud)

换句话说,这是Java 7和8之间的向后源不兼容.我已经了解了Java SE 8和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)
Run Code Online (Sandbox Code Playgroud)

nos*_*sid 40

Java语言规范在类型推断方面发生了显着变化.在JLS7中,类型推断§15.12.2.7§15.12.2.8中描述,而在JLS8中,有一整章专用于第18章类型推断.

在JLS7和JLS8中,规则都非常复杂.很难区分差异,但显然存在差异,如第18.5.2所示:

这种推理策略不同于Java语言规范的Java SE 7版[..].

但是,改变的目的是向后兼容.参见§18.5.2节的最后一段:

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

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

new Acceptor<>(new Impl());
Run Code Online (Sandbox Code Playgroud)

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

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

这也是为什么下面的陈述有效的原因.

Acceptor<?> acceptor = (Acceptor<?>) new Acceptor<>(new Impl());
Run Code Online (Sandbox Code Playgroud)

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

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

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

Acceptor<?> acceptor = new Acceptor<>(new Impl());
Run Code Online (Sandbox Code Playgroud)


Vic*_*ero 20

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


Ale*_*sky 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; }
Run Code Online (Sandbox Code Playgroud)

以下现在在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)
Run Code Online (Sandbox Code Playgroud)

当然,这在以下两个方面都给出了错误:

Acceptor<Impl> a = new Acceptor<Impl2>(new Impl2());
Run Code Online (Sandbox Code Playgroud)

这在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>
Run Code Online (Sandbox Code Playgroud)

以下两者都给出了错误,但错误不同:

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
Run Code Online (Sandbox Code Playgroud)

显然,Java 8使类型推理系统更加智能化.这会导致不兼容吗?一般来说,没有.由于类型擦除,只要程序编译,实际上并不重要的是推断出什么类型.Java 8是否编译所有Java 7程序?它应该,但你提出了一个案例,但它没有.

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

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

Acceptor<?> a = new Acceptor<>(new Impl2());
Run Code Online (Sandbox Code Playgroud)

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