什么是Java中的二进制兼容性?

我正在阅读Joshua Bloch撰写的Effective Java 。

在第17项:“仅使用接口定义类型”中,我遇到了不建议使用接口存储常量的解释。 我在下面解释。

“更糟糕的是,它代表了一种承诺:如果在将来的版本中修改了类以便它不再需要使用常量,它仍然必须实现接口以确保二进制兼容性。”

二进制兼容性意味着什么?

有人可以用Java中的示例指导我,以表明代码是二进制兼容的。

简而言之,二进制兼容性意味着当您更改类时,不需要重新编译使用它的类。 例如,您从此类中删除或重命名了公共或受保护的方法

public class Logger implements Constants { public Logger getLogger(String name) { return LogManager.getLogger(name); } } 

从您的log-1.jar库中发布了一个新版本作为log-2.jar。 当您的log-1.jar用户下载新版本时,当他们尝试使用缺少的getLogger(String name)方法时,它将破坏他们的应用程序。

如果删除常量接口(第17项),由于相同的原因,这将破坏二进制兼容性。

但是,您可以删除/重命名此类的私有或包私有成员,而不会破坏二进制兼容性,因为外部应用程序不能(或不应该)使用它。

为了更好地理解这个概念,有趣的是, 二进制兼容性并不意味着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 

将版本1替换为版本2:删除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指令: getstaticgetfield 。 这个突破在JLS 7 13.4.10中提到:

如果未声明为private的字段未声明为static且更改为声明为static,反之亦然,则如果字段由预期存在的二进制文件使用,则会产生链接错误(特别是IncompatibleClassChangeError)另一种

我们需要使用javac Main.java重新编译Main以使其与新版本一起使用。

笔记:

  • 从类实例中调用静态成员,比如(new Lib()).i的样式很糟糕,引发警告,永远不应该完成
  • 这个例子是设计的,因为非静态的final基元是无用的:总是对基元使用static final : private final static attribute vs private final attribute
  • reflection可以用来看到差异 。 但reflection也可以看到私人领域,这显然会导致rest,而这些rest不应该被视为rest,所以它不计算在内。

二进制兼容但不兼容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); } } 

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

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

笔记:

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

二进制兼容性

Java二进制兼容性规定了类的修改和重新编译不需要重新编译导入已修改类的其他类的条件。 二进制兼容性是语言设计的新概念。

Java语言规范[7]描述了二进制兼容的更改,如下所示:

如果先前存在的没有错误链接的预先存在的二进制文件将继续无错误地链接,则对类型的更改是二进制兼容的(等效地,不会破坏与预先存在的二进制文件的兼容性)。

如果将来,我们希望更改某些类正在实现的接口(例如,添加一些新方法)。

如果我们添加抽象方法( 附加方法 ),那么类(实现接口) 必须实现创建依赖性约束的额外方法和执行相同的成本开销。

为了解决这个问题,我们可以在界面中添加默认方法。

这将删除实现其他方法的依赖项。

我们不需要修改实现类来合并更改。 这称为二进制兼容性。

请参考以下示例:

我们将要使用的界面

  //Interface interface SampleInterface { // abstract method public void abstractMethod(int side); // default method default void defaultMethod() { System.out.println("Default Method Block"); } // static method static void staticMethod() { System.out.println("Static Method Block"); } } //The Class that implements the above interface. class SampleClass implements SampleInterface { /* implementation of abstractMethod abstract method, if not implemented will throw compiler error. */ public void abstractMethod(int side) {System.out.println(side*side);} public static void main(String args[]) { SampleClass sc = new SampleClass(); sc.abstractMethod(4); // default method executed sc.defaultMethod(); // Static method executed SampleInterface.staticMethod(); } } 

注意:有关更多详细信息,请参阅默认方法

使事情看起来很简单:

可以运行用于在另一台计算机上运行的相同二进制代码的计算机被认为是二进制兼容的。 这与源代码兼容性不同,后者可能需要重新编译。

在开发要在多个操作系统上运行的计算机程序时,二进制兼容性是一个主要的好处。