54

我正在阅读Joshua Bloch 的《 Effective Java 》。

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

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

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

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

4

5 回答 5

52

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

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) 方法时,它将破坏他们的应用程序。

而且,如果您删除 Constants 接口(第 17 项),由于同样的原因,这也会破坏二进制兼容性。

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

于 2013-02-20T06:23:38.930 回答
33

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

发生这种情况是因为即使我们可以用(new Lib()).iJava 编写这两种static方法和成员方法,它也会编译成两个不同的 VM 指令,具体取决于Lib:getstaticgetfield. JLS 7 13.4.10提到了这个中断:

如果未声明为私有的字段未声明为静态并且更改为声明为静态,反之亦然,如果该字段被预期字段的预先存在的二进制文件使用,则将导致链接错误,特别是 IncompatibleClassChangeError另一种。

我们需要重新编译它才能Main使用javac Main.java新版本。

笔记:

  • 从类实例中调用静态成员(new Lib()).i是不好的风格,会引发警告,并且永远不应该这样做
  • 这个例子是人为的,因为非静态final原语是无用的:总是static final用于原语:私有最终静态属性与私有最终属性
  • 反射可以用来看出差异。但是反射也可以看到私有字段,这显然会导致不应该算作中断的中断,所以不算。

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

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

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

笔记:

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

C 二进制兼容性示例

什么是应用程序二进制接口 (ABI)?

于 2015-03-27T20:51:44.640 回答
7

二进制兼容性

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

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

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

于 2013-02-20T06:22:27.540 回答
1

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

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

为了克服这个问题,我们可以在接口中添加默认方法。

这将删除实现附加方法的依赖项。

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

请参考以下示例:

我们将要使用的界面

    //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();

        }
    }

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

于 2017-10-18T07:18:35.620 回答
-3

为了让事情看起来简单:

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

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

于 2013-02-20T06:16:29.170 回答