何为二进制兼容,替换一个应用程序的部分源代码而对程序原有功能不产生破坏,那么这个替换的代码是具有兼容性的。
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
将版本1替换为版本2:删除static:
public class Lib {public final int i = 1;}
重新编译版本2,不编译客户端代码,然后运行java Main:
javac Lib.javajava Main
我们得到:
Exception in thread "main" java.lang.IncompatibleClassChangeError: Expected static field Lib.iat 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:
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);}}
这一次,即使在更新Main后重新编译Lib,第二次调用也会抛出,但不是第一次调用。
这是因为我们以一种在Java编译时无法检查的方式更改了method的契约:在null之后,不再是null。
注意:
- Eclipse wiki是一个很好的来源:https://wiki.eclipse.org/Evolving_Java-based_APIs
 - 制作接受{{1}}值的API是一个值得怀疑的做法
 - 更改会破坏API兼容性而不是二进制更容易,反之亦然,因为很容易更改方法的内部逻辑
 
说实话,这种二进制兼容只能说是不完全的兼容
