将枚举更改为类会破坏二进制兼容性吗

时间:2018-07-08 14:58:04

标签: java class enums binary-compatibility

如果我有这样的枚举:

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就能不用重新编译就可以继续使用它吗?

2 个答案:

答案 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节),则是编译时错误。