【问题标题】:Why does this compile in Java7 and does not in Java8?为什么这在 Java 7 中可以编译,而在 Java 8 中却没有?
【发布时间】:2014-12-03 19:56:28
【问题描述】:

泛型很棘手。 并且看起来它们在不同版本的 Java 中的处理方式不同。

此代码在 Java 7 中成功编译,在 Java 8 中编译失败。

import java.util.EnumSet;

public class Main {
  public static void main(String[] args) {
    Enum foo = null;
    tryCompile(EnumSet.of(foo));
  }

  static <C extends Enum<C> & Another> void tryCompile(Iterable<C> i) {}

  static interface Another {}
}

这是来自 Java 8 的错误消息。我用这个来编译它:http://www.compilejava.net/

/tmp/java_A7GNRg/Main.java:6: error: method tryCompile in class Main cannot be applied to given types;
    tryCompile(EnumSet.of(foo));
    ^
  required: Iterable<C>
  found: EnumSet
  reason: inferred type does not conform to upper bound(s)
    inferred: Enum
    upper bound(s): Enum<Enum>,Another
  where C is a type-variable:
    C extends Enum<C>,Another declared in method <C>tryCompile(Iterable<C>)
/tmp/java_A7GNRg/Main.java:6: warning: [unchecked] unchecked method invocation: method of in class EnumSet is applied to given types
    tryCompile(EnumSet.of(foo));
                         ^
  required: E
  found: Enum
  where E is a type-variable:
    E extends Enum<E> declared in method <E>of(E)
1 error
1 warning

问题是关于Java编译器版本之间的差异。

【问题讨论】:

  • Java 8 抱怨什么?
  • 确实,Java 8 中的编译错误应该解释为什么它无法编译。它很可能会.​​.....
  • 你希望这段代码做什么?此外,您的 Enum 是原始类型。
  • 如果没有错误消息,很难猜出这里发生了什么,但我猜编译器能够(正确)推断 EnumSet.of(foo) 的类型为 EnumSet&lt;Enum&gt; 不兼容绑定C extends Enum&lt;C&gt; &amp; Another.
  • 完成,错误输出已添加到问题中。但它有什么帮助?

标签: java generics compiler-errors java-7 java-8


【解决方案1】:

Java 7 和 Java 8 的主要区别在于目标类型推断。 Java 7 只考虑方法调用的参数来确定类型参数,Java 8 将使用表达式的目标类型,即在嵌套方法调用的情况下的参数类型,初始化或分配的变量的类型到,或者在 return 语句的情况下方法的返回类型。

例如在编写 List&lt;Number&gt; list=Arrays.asList(1, 2, 3, 4); 时,Java 7 将通过查看方法的参数来推断右侧的类型 List&lt;Integer&gt; 并生成错误,而 Java 8 将使用目标类型 List&lt;Number&gt; 来推断方法参数的约束必须是Number 的实例,就是这种情况。因此,它在 Java 8 中是合法的。

如果你对正式的细节感兴趣,你可以研究“Java Language Specification, Chapter 18. Type Inference”,尤其是§18.5.2. Invocation Type Inference,但是,这并不容易阅读……

那么当你说Enum foo = null; tryCompile(EnumSet.of(foo)); 时会发生什么?

在 Java 7 中,表达式 EnumSet.of(foo) 的类型将通过查看参数的类型来推断,foo 是原始类型 Enum,因此将执行未经检查的操作,结果类型为原始类型EnumSet。这种类型实现了原始类型Iterable,因此可以传递给tryCompile,形成另一个未经检查的操作。

在 Java 8 中,EnumSet.of(foo) 的目标类型是 tryCompile 的第一个参数的类型,即 Iterable&lt;C extends Enum&lt;C&gt; &amp; Another&gt;,因此无需过多介绍,在 Java 7 中,EnumSet.of 将被视为原始类型调用,因为它有一个原始类型参数,在 Java 8 中它将被视为泛型调用,因为它具有泛型目标类型。通过将其视为泛型调用,编译器将得出结论,找到的类型 (Enum) 与所需的类型 C extends Enum&lt;C&gt; &amp; Another 不兼容。虽然您可以通过未经检查的警告将原始类型 Enum 分配给 C extends Enum&lt;C&gt;,但它会被认为与 Another 不兼容(没有类型转换)。

你确实可以插入这样的演员表:

Enum foo = null;
tryCompile(EnumSet.of((Enum&Another)foo));

由于将Enum 分配给C extends Enum&lt;C&gt;,因此编译当然不会没有未经检查的警告。

您还可以分解目标类型关系,以便执行与 Java 7 中相同的步骤:

Enum foo = null;
EnumSet set = EnumSet.of(foo);
tryCompile(set);

这里,原始类型在三行中使用,因此编译时会出现未经检查的警告,并且与 Java 7 中一样对 implements Another 约束无知。

【讨论】:

  • 这是一个很棒的解释。你有没有机会知道我可以阅读的资料,以便能够从我的脑海中得到类似的东西?是的,不是 JLS,而是更...可读的东西。
  • 这里有目标类型herehere的简单介绍。对于一般的“Java 8 中的新功能”,您可以查看 here。您可能会发现互联网上的许多其他网站都使用其他词语和更多示例进行了类似的说明,但是,我不知道有哪个网站可以在不过于正式的情况下深入得多。
【解决方案2】:

Java 8 中的类型推断引擎已得到改进,并且(我假设)现在能够确定 C 类型不扩展 Another

在 Java 7 中,类型推断系统无法或无法确定 Another 类型是否丢失,并为程序员提供了怀疑的好处(在编译时)。

如果您在 Java 7 的运行时调用 Another 接口上的方法,您仍然需要为运行时的违规行为付费。

例如这段代码:

import java.util.EnumSet;

public class Main {

  static enum Foo {
    BAR
  }

  public static void main(String[] args) {
    Enum foo = Foo.BAR;
    tryCompile(EnumSet.of(foo));
  }

  static <C extends Enum<C> & Another> void tryCompile(Iterable<C> i) {
    i.iterator().next().doSomething();
  }

  static interface Another {
    void doSomething();
  }
}

运行时会产生这个错误:

Exception in thread "main" java.lang.ClassCastException: Main$Foo cannot be cast to Main$Another
    at Main.tryCompile(Main.java:16)
    at Main.main(Main.java:12)

即使 Java 7 编译器会编译代码,它仍然会发出有关原始类型和未经检查的调用的警告,这应该会提醒您有什么不对劲。


这是一个非常简单的示例,它没有使用枚举,而是以 Enum 的定义为模型,它表现出同样的问题。在 Java 7 中编译时出现警告,但在 Java 8 中没有:

import java.util.Collections;
import java.util.List;

public class Main {

  static class Foo<T extends Foo<T>> {
  }

  static class FooA extends Foo<FooA> {
  }

  public static <T extends Foo<T>> List<T> fooList(T e) {
    return Collections.singletonList(e);
  }


  public static void main(String[] args) {
    Foo foo = new FooA();
    tryCompile(fooList(foo));
  }

  static <C extends Enum<C> & Another> void tryCompile(Iterable<C> i) {
    i.iterator().next().doSomething();
  }

  static interface Another {
    void doSomething();
  }
}

所以这不是Enum 特定的问题,但可能是因为涉及到递归类型。

【讨论】:

  • 不,预计会出现运行时异常。我同意。但是,如果您尝试用您的 Foo 替换 Enum,编译会中断对另一个的说明。所以这是 Enum 特定的问题,据我所知。
  • 是的,但是编译器可以确定Foo 没有实现Another 接口。我不认为它是特定于枚举的,但可能特定于具有类型参数的类。让我检查一下。
【解决方案3】:

在我看来是一个正确的错误:

reason: inferred type does not conform to upper bound(s)
  inferred: Enum
  upper bound(s): Enum<Enum>,Another

EnumSet.of(foo) 的类型为EnumSet&lt;Enum&gt;,它与C extends Enum&lt;C&gt; &amp; Another 不兼容,原因与Set&lt;Enum&gt;Set&lt;? extends Enum&gt; 不兼容的原因相同,因为Java 泛型是不变的。

【讨论】:

  • 那么它不会在 Java 7 中编译。它会。
  • Java 中的枚举类型为Enum&lt;E extends Enum&lt;E&gt;&gt;,因此据我所知,它与C extends Enum&lt;C&gt; 匹配。
  • 我目前最好的猜测是这是一个 rawtypes 问题。如果您将 foo 显式键入为Enum&lt;Enum&gt;,它会起作用吗?
  • compilejava.net试试看,太棒了! Enum 不是有效类型,Enum> 也无法编译。
【解决方案4】:

这对我来说在 Eclipse 标准/SDK 版本中编译得很好:Luna Release (4.4.0) Build id: 20140612-0600 with the Eclipse JDT (Java Development Tools) Patch with Java 8 support (for Kepler SR2) 1.0.0 .v20140317-1956 org.eclipse.jdt.java8patch.feature.group Eclipse.org 已安装。

我收到一些警告(foo 上的原始类型和 tryCompile 上的未经检查的调用。

【讨论】:

  • 如果你收到警告,它将编译为 java 7.. 你需要编译为 java 8 完成
  • 哎呀,看来我撒了谎。你是对的,我正在编译到 1.7 :(
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-31
  • 2016-04-22
  • 2021-06-14
  • 2021-08-15
相关资源
最近更新 更多