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