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


77

该程序可以在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());
    }
}

结果:

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之间名单,但没有发现任何东西,将适合我的问题。

那么,这是一个错误吗?

环境:

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


4
Acceptor<?> acceptor = new Acceptor<Impl>(new Impl())应该工作正常还是Acceptor<Impl> acceptor = new Acceptor<>(new Impl())
Edwin Dalorzo

对我来说听起来像个虫子。似乎钻石操作员感到困惑。
Bhesh Gurung 2014年

这对我来说很好。1月使用oracle.com/technetwork/articles/java/lambda-1984522.html中的下载
Ray Tayek 2014年

2
@AleksandrDubinsky为什么?
ghik 2014年

Answers:



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语言规范的Java SE 7版中的算法向后兼容。

我不能说那是真的。但是,您的代码有一些有趣的变体,没有显示出该问题。例如,以下语句可正确编译:

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());

感谢您的详尽解释。我知道了解决方法,但决定我已经明白,为什么好,我让我们的代码库大编译错误,将其迁移到Java 8之前
ghik

我会假设最后一条语句会导致错误,因为new Acceptor<?>(...)这两个版本均不允许。如果有的话,它是java7的类型推断中的错误
aepurniet 2014年

@aepurniet Java 7将其推断为new Acceptor<Impl>(...)。AFAIK,Java 7类型推断永远不会考虑预期的结果类型。
ghik 2014年

List<Integer> list = new ArrayList<>();由于前面的语句没有足够的信息来进行推断,因此必须这样做。
aepurniet

@aepurniet哦,对了,没关系。Diamond推断与方法类型参数推断不同。
ghik 2014年

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());

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


-1,new Foo<>(..)绝对会考虑使用构造函数参数进行推断。
塔维安·巴恩斯

@TavianBarnes在乎提供援引吗?
Aleksandr Dubinsky

@TavianBarnes你是对的。看起来在Java 8中,类型推断同时针对构造函数和方法都同时检查了参数和目标类型。但是,您对-1有点不公平,因为仅在我的答案中有一部分是错误的,并且我以AFAIK作为开头。
Aleksandr Dubinsky 2014年

公平地说,一旦您编辑答案,我将删除-1。Java 7还使用构造函数参数进行推断。
塔维安·巴恩斯

@TavianBarnes我已经编辑了答案。我学到了很多东西,谢谢。
Aleksandr Dubinsky 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.