为了更好地理解这个概念,有趣的是,二进制兼容性并不意味着 API 兼容性,反之亦然。
API 兼容但不兼容二进制:静态删除
库版本 1:
public class Lib {
public static final int i = 1;
}
客户端代码:
public class Main {
public static void main(String[] args) {
if ((new Lib()).i != 1) throw null;
}
}
使用版本 1 编译客户端代码:
javac Main.java
用版本2替换版本1:删除static:
public class Lib {
public final int i = 1;
}
重新编译只是版本2,不是客户端代码,然后运行java Main:
javac Lib.java
java Main
我们得到:
Exception in thread "main" java.lang.IncompatibleClassChangeError: Expected static field Lib.i
at Main.main(Main.java:3)
发生这种情况是因为即使我们可以在 Java 中为 static 和成员方法编写 (new Lib()).i,它也会根据 Lib 编译成两个不同的 VM 指令:getstatic 或 getfield。 JLS 7 13.4.10 提到了这个休息时间:
如果未声明为私有的字段未声明为静态并且更改为声明为静态,反之亦然,如果该字段被预先存在的二进制文件使用,则会导致链接错误,特别是 IncompatibleClassChangeError期望其他类型的字段。
我们需要重新编译 Main 和 javac Main.java 才能使其与新版本一起使用。
注意事项:
二进制兼容但不兼容 API:空前置条件强化
版本 1:
public class Lib {
/** o can be null */
public static void method(Object o) {
if (o != null) o.hashCode();
}
}
版本 2:
public class Lib {
/** o cannot be null */
public static void method(Object o) {
o.hashCode();
}
}
客户:
public class Main {
public static void main(String[] args) {
Lib.method(null);
}
}
这一次,即使更新Lib后重新编译Main,第二次调用也会抛出,但不会第一次。
这是因为我们以一种 Java 在编译时无法检查的方式更改了 method 的合同:在它可以占用 null 之前,之后不再。
注意事项:
C 二进制兼容性示例
What is an application binary interface (ABI)?