为什么这在Java 7中编译而在Java 8中不编译?

时间:2014-10-09 01:44:13

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

泛型很棘手。 看起来它们在不同版本的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编译器版本之间的区别。

4 个答案:

答案 0 :(得分:12)

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

E.g。在编写List<Number> list=Arrays.asList(1, 2, 3, 4);时,Java 7将通过查看方法的参数推断右侧的类型List<Integer>并生成错误,而Java 8将使用目标类型List<Number>来推断方法参数必须是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<C extends Enum<C> & Another>的类型,因此在Java 7 {{1}中没有太多细节将被视为原始类型调用,因为它具有原始类型参数,在Java 8中,它将被视为通用调用,因为它具有通用目标类型。通过将其视为通用调用,编译器将断定找到的类型(EnumSet.of)与所需类型Enum不兼容。虽然您可以通过未经检查的警告将原始类型C extends Enum<C> & Another分配给Enum,但会认为它与C extends Enum<C>不兼容(没有类型转换)。

你确实可以插入这样一个演员:

Another

由于Enum foo = null; tryCompile(EnumSet.of((Enum&Another)foo)); 已分配给Enum,因此编译当然不会没有未经检查的警告。

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

C extends Enum<C>

这里,在三行中使用了原始类型,因此这将使用未经检查的警告和与Java 7中Enum foo = null; EnumSet set = EnumSet.of(foo); tryCompile(set); 约束相同的无知来编译。

答案 1 :(得分: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特定问题,但可能是因为涉及的递归类型。

答案 2 :(得分:0)

对我来说看起来是错误的错误:

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

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

答案 3 :(得分:-1)

这在Eclipse Standard / SDK版本中编译很好:Luna Release(4.4.0)使用Eclipse JDT(Java开发工具)修补程序构建id:20140612-0600支持Java 8(适用于Kepler SR2)1.0.0 .v20140317-1956 org.eclipse.jdt.java8patch.feature.group安装了Eclipse.org。

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

相关问题