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

Sam*_*Sam 40 java

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

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

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

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

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

Evg*_*eev 37

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

public class Logger implements Constants {
   public Logger getLogger(String name) {
         return LogManager.getLogger(name);
   }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • Richard E. Little在他的[博客]上有一个非常简单,好的例子(http://codefhtagn.blogspot.kr/2010/11/java-binary-compatibility-more-than.html).它真的**显示二进制兼容性问题而不是源代码兼容性问题,如本答案所示(使用重命名方法). (3认同)
  • 令我惊讶的是,官方文档无法以这种简单程度来解释其内容。我不确定他们为什么要使用律师通常在条款和协议中使用的类似语言。 (2认同)

Cir*_*四事件 19

为了更好地理解这个概念,有趣的是,二进制兼容性并不意味着API兼容性,反之亦然.

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

图书馆版本1:

public class Lib {
    public static final int i = 1;
}
Run Code Online (Sandbox Code Playgroud)

客户代码:

public class Main {
    public static void main(String[] args) {
        if ((new Lib()).i != 1) throw null;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

javac Main.java
Run Code Online (Sandbox Code Playgroud)

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

public class Lib {
    public final int i = 1;
}
Run Code Online (Sandbox Code Playgroud)

仅重新编译版本2,而不是客户端代码,并运行java Main:

javac Lib.java
java Main
Run Code Online (Sandbox Code Playgroud)

我们得到:

Exception in thread "main" java.lang.IncompatibleClassChangeError: Expected static field Lib.i
        at Main.main(Main.java:3)
Run Code Online (Sandbox Code Playgroud)

发生这种情况是因为即使我们可以用(new Lib()).iJava static和成员方法编写Java ,它也会根据以下内容编译为两个不同的VM指令Lib:getstatic或getfield.这个突破在JLS 7 13.4.10中提到:

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

我们需要重新编译Main与javac Main.java为它与新的版本.

笔记:

  • 从类实例调用静态成员就像(new Lib()).i是坏样式,引发警告,永远不应该完成
  • 这个例子是设计的,因为非静态final原语是无用的:总是static final用于原语: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();
    }
}
Run Code Online (Sandbox Code Playgroud)

版本2:

public class Lib {
    /** o cannot be null */
    public static void method(Object o) {
        o.hashCode();
    }
}
Run Code Online (Sandbox Code Playgroud)

客户:

public class Main {
    public static void main(String[] args) {
        Lib.method(null);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

这是因为我们method以一种在Java编译时无法检查的方式改变了合同:在它可以采取之前null,之后不再这样做了.

笔记:

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

  • 恕我直言,第一个示例是不正确的,因为您删除了 `static`,现在它与 API 不兼容(即,字节码中的 `Lib.i` 不再起作用)。推断某些代码的 API 兼容性不取决于客户端是否使用此 API,而是取决于 API 兼容性的标准(或规范)。虽然我同意 API 兼容性并不意味着二进制兼容性。 (2认同)

Sum*_*ngh 6

二进制兼容性

Java的二进制兼容性规定了实施modication和类没有必要进一步阶级的重新编译重新编译导入 - 荷兰国际集团的modied类的条件.二进制兼容性是语言设计的新概念.

在Java语言specication [7]描述了如下二进制相兼容的变化:

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