为了更好地理解这个概念,有趣的是看到二进制兼容性并不意味着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中我们可以使用
(new Lib()).i
来调用静态和成员方法,但根据
Lib
的不同,它将编译为两个不同的VM指令:
getstatic
或
getfield
。这种情况被提到在
JLS 7 13.4.10中:
如果一个未声明为private的字段未被声明为static并且被更改为声明为static,或者反之,则链接错误,特别是IncompatibleClassChangeError,将导致如果预期其他类型的字段的现有二进制文件使用该字段。
我们需要使用javac Main.java
重新编译Main
以使其与新版本一起工作。
注:
- 像
(new Lib()).i
这样从类实例调用静态成员是不良风格,会引发警告,并且绝不能使用。
- 此示例是人为的,因为非静态
final
基元无用:始终对基元使用static final
:私有final static属性与私有final属性
- 可以使用反射查看区别。但是,反射还可以查看私有字段,这显然会导致不应计为中断的中断,因此不计入在内。
二进制兼容但API不兼容:空前提强化
版本1:
public class Lib {
public static void method(Object o) {
if (o != null) o.hashCode();
}
}
版本2:
public class Lib {
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 二进制兼容性示例
什么是应用程序二进制接口(ABI)?