为什么这在Java7中编译而在Java8中不编译?

generics是棘手的。 看起来它们在不同版本的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 & Another> void tryCompile(Iterable 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 found: EnumSet reason: inferred type does not conform to upper bound(s) inferred: Enum upper bound(s): Enum,Another where C is a type-variable: C extends Enum,Another declared in method tryCompile(Iterable) /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 declared in method of(E) 1 error 1 warning 

问题是关于Java编译器版本之间的区别。

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

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

如果您对正式的详细信息感兴趣,可以学习“Java语言规范,第18章类型推断” ,尤其是§18.5.2。 然而, 调用类型推断并不容易阅读……

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

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

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

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

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

这个编译当然不是没有未经检查的警告,因为Enum to C extends Enum的分配C extends Enum

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

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

在这里,原始类型在整个三行中使用,因此这将编译未经检查的警告和对implements Another的相同无知与Java 7中的implements Another约束。

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  & Another> void tryCompile(Iterable 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> { } static class FooA extends Foo { } public static > List fooList(T e) { return Collections.singletonList(e); } public static void main(String[] args) { Foo foo = new FooA(); tryCompile(fooList(foo)); } static  & Another> void tryCompile(Iterable i) { i.iterator().next().doSomething(); } static interface Another { void doSomething(); } } 

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

看起来对我来说是一个正确的错误:

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

EnumSet.of(foo)将具有类型EnumSet ,它与C extends Enum & Another不兼容,原因与SetSet不兼容的原因相同Set Set ,因为Javagenerics是不变的。

这在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调用。