何为二进制兼容,替换一个应用程序的部分源代码而对程序原有功能不产生破坏,那么这个替换的代码是具有兼容性的。

API兼容但非二进制兼容:静态删除

图书馆第1版:

  1. public class Lib {
  2. public static final int i = 1;
  3. }


客户代码:

  1. public class Main {
  2. public static void main(String[] args) {
  3. if ((new Lib()).i != 1) throw null;
  4. }
  5. }


使用版本1编译客户端代码:

  1. javac Main.java


将版本1替换为版本2:删除static

  1. public class Lib {
  2. public final int i = 1;
  3. }


重新编译版本2,不编译客户端代码,然后运行java Main

  1. javac Lib.java
  2. java Main


我们得到:

  1. Exception in thread "main" java.lang.IncompatibleClassChangeError: Expected static field Lib.i
  2. at Main.main(Main.java:3)


Main.java并没有被重新编译,获取到Lib 的 i 常量的vm命令仍是getstatic,而新版本Lib.java被编译后,i变量不是静态变量,所以会导致getstatic会出错。

我们需要使用Main重新编译javac Main.java才能使用新版本。

注意:

  • (new Lib()).i这样的类实例调用静态成员是不好的风格,引发警告,永远不要编写这样的代码。
  • 这个例子是设计的,public final int i = 1 在实际开发中应该使用public static final int i = 1 ,因为i与类相关而不是实例,没必要为每个实例去备份同样的东西,而应该使用共享的方式。参考:private final static attribute vs private final attribute

二进制兼容但不兼容API:null前置条件强化


版本1:

  1. public class Lib {
  2. /** o can be null */
  3. public static void method(Object o) {
  4. if (o != null) o.hashCode();
  5. }
  6. }


第2版:

  1. public class Lib {
  2. /** o cannot be null */
  3. public static void method(Object o) {
  4. o.hashCode();
  5. }
  6. }


客户端:

  1. public class Main {
  2. public static void main(String[] args) {
  3. Lib.method(null);
  4. }
  5. }


这一次,即使在更新Main后重新编译Lib,第二次调用也会抛出,但不是第一次调用。

这是因为我们以一种在Java编译时无法检查的方式更改了method的契约:在null之后,不再是null

注意:

  • Eclipse wiki是一个很好的来源:https://wiki.eclipse.org/Evolving_Java-based_APIs
  • 制作接受{{1}}值的API是一个值得怀疑的做法
  • 更改会破坏API兼容性而不是二进制更容易,反之亦然,因为很容易更改方法的内部逻辑

说实话,这种二进制兼容只能说是不完全的兼容