为什么这在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
,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
,所以在Java 7 EnumSet.of
没有太多细节。将被视为原始类型调用,因为它具有原始类型参数,在Java 8中,它将被视为通用调用,因为它具有通用目标类型。 通过将其视为通用调用,编译器将得出结论:找到的类型( Enum
)与所需类型C extends Enum
不兼容, C extends Enum
。 虽然您可以通过未经检查的警告将原始类型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
不兼容,原因与Set
与Set extends Enum>
不兼容的原因相同Set extends Enum>
Set extends Enum>
,因为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调用。