如果我有这样的枚举:
listen
如果将二进制兼容更改为以下内容,是否会破坏二进制兼容?
public enum Test {
TEST_VALUE(42), OTHER_TEST_VALUE(1337);
private int val;
Test(int val) {
this.val = val;
}
public void increment() {
val++;
}
public int getVal() {
return val;
}
并将所有其他枚举方法(如“值”和“普通”)添加到新类中?这样其他jar就能不用重新编译就可以继续使用它吗?
答案 0 :(得分:2)
我从这个枚举开始做了一些测试
public enum Test {
TEST_VALUE(42), OTHER_TEST_VALUE(1337);
private int val;
Test(int val) {
this.val = val;
}
public void increment() {
val++;
}
public int getVal() {
return val;
}
}
然后使用此类进行测试:
公共类EnumTesting {
public static void main(String[] args) {
for (Test enumClass : Test.values()) {
enumClass.increment();
}
System.out.println(Test.OTHER_TEST_VALUE.name() + Test.OTHER_TEST_VALUE.getVal());
System.out.println(Test.TEST_VALUE.name() + Test.TEST_VALUE.getVal());
System.out.println("For name : " + Test.valueOf("TEST_VALUE"));
System.out.println(Test.OTHER_TEST_VALUE.ordinal());
}
}
然后,我编译这些类,并复制EnumTesting类以供将来参考。 我将测试枚举更改为:
public class Test {
private static int globalOrdinal = 0;
public static final Test TEST_VALUE = new Test(42, "TEST_VALUE");
public static final Test OTHER_TEST_VALUE = new Test(1337, "OTHER_TEST_VALUE");
private int val;
private final String oldEnumName;
private int ordinal;
private Test(int val, String oldEnumName) {
this.val = val;
this.oldEnumName = oldEnumName;
this.ordinal = globalOrdinal++;
}
public void increment() {
val++;
}
public int getVal() {
return val;
}
public String name() {
return oldEnumName;
}
@Override
public String toString() {
return oldEnumName;
}
public int ordinal() {
return this.ordinal;
}
public static final Test[] VALUES = new Test[] {TEST_VALUE, OTHER_TEST_VALUE};
public static Test[] values() {
return VALUES;
}
public static Test valueOf(String oldEnumName) {
for (Test enumClass : VALUES) {
if (enumClass.oldEnumName.equals(oldEnumName))
{
return enumClass;
}
}
throw new IllegalArgumentException();
}
}
并重新组合了所有内容。重新编译后,EnumTesting类的字节码保持不变。因此,这确实是二进制兼容的更改。
答案 1 :(得分:0)
这不是不是的向后兼容更改,因为新类将不是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 did 起作用了!事实证明,切换实际上是由编译器为查找创建的匿名类处理的,归结为常规和静态字段。这让我有些惊讶!
但是其他三个用例急剧增加,并且如果我还不知道根本原因,错误消息将使您非常困惑。以下是来自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节),则是编译时错误。