这不是向后兼容的更改,因为新类不会是 java.lang.Enum 的子类。
这确实很重要,因为任何时候将泛型定义为<E extends Enum<E>>,字节码都需要实际类型是 Enum 的子类。特别是,EnumSet 和 EnumMap 不起作用。 (其他类似的课程也可能会中断——但这两个很常见,我认为它们值得特别提及。)
为了测试这一点,我从一个枚举类和它的四个用户开始:
public enum SoEnum {
FOO, BAR
}
public class SoUser {
public static void main(String[] args) {
SoEnum e = SoEnum.FOO;
switch (e) {
case FOO:
System.out.println("a foo");
break;
default:
System.out.println(e.name());
}
}
}
import java.util.*;
public class SoUserEnumMap {
public static void main(String[] args) {
EnumMap<SoEnum,String> map = new EnumMap<>(SoEnum.class);
map.put(SoEnum.FOO, "the foo");
System.out.println(map);
}
}
import java.util.*;
public class SoUserEnumSet {
public static void main(String[] args) {
EnumSet<SoEnum> set = EnumSet.of(SoEnum.FOO);
System.out.println(set);
}
}
public class SoUserGeneric {
public static void main(String[] args) {
String s = getName(SoEnum.FOO);
System.out.println(s);
}
static <E extends Enum<E>> String getName(E e) {
return e.name();
}
}
我编译了所有这些,然后将 SoEnum.java 替换为使用普通类的版本。 (我写了一个简单的例子,所以有点难看:)):
public class SoEnum {
private SoEnum() {}
public static SoEnum FOO = new SoEnum();
public static SoEnum BAR = new SoEnum();
public static SoEnum[] values() {
return new SoEnum[] { FOO, BAR };
}
public int ordinal() {
return this == FOO ? 0 : 1;
}
public String name() {
return this == FOO ? "FOO" : "BAR";
}
@Override
public String toString() {
return name();
}
}
我只重新编译了 SoEnum.java,然后运行了其余的。有趣的是,SoUser 确实工作了!事实证明,switch 实际上是由编译器为查找创建的匿名类处理的,它归结为序数和静态字段。这让我有点吃惊!
但是其他三个用例爆炸性地爆炸了,如果我还不知道根本原因,错误消息会非常令人困惑。以下是来自java SoUserEnumSet 的错误信息。其他两个用户类以类似的方式失败(即使是 SoUserGeneric,我认为它会在稍微更标准的 ClassCastException 中失败)。
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
Exception Details:
Location:
SoUserEnumSet.main([Ljava/lang/String;)V @3: invokestatic
Reason:
Type 'SoEnum' (current frame, stack[0]) is not assignable to 'java/lang/Enum'
Current Frame:
bci: @3
flags: { }
locals: { '[Ljava/lang/String;' }
stack: { 'SoEnum' }
Bytecode:
0x0000000: b200 02b8 0003 4cb2 0004 2bb6 0005 b1
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
at java.lang.Class.privateGetMethodRecursive(Class.java:3048)
at java.lang.Class.getMethod0(Class.java:3018)
at java.lang.Class.getMethod(Class.java:1784)
at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:544)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:526)
请注意,由于这是一个链接错误,“行”号 (@3) 甚至与源代码行不对应——它是 .class 文件中的操作码偏移量。
另请注意,JLS 8.1.4 指定您不能创建显式扩展 Enum 的类(也就是说,您不能通过将 class SoEnum 更改为 class SoEnum extends Enum<SoEnum> 来修复它):
如果 ClassType 将类命名为 Enum 或对 Enum 的任何调用(第 8.9 节),则会出现编译时错误。