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

64
我正在阅读 Joshua Bloch 的 Effective Java 一书。在第17条中,"仅使用接口定义类型",我看到了一些解释,其中不建议使用接口来存储常量。下面是该解释:
"更糟糕的是,它代表了一种承诺:如果在未来的版本中修改了类,以便不再需要使用这些常量,仍然必须实现接口以确保二进制兼容性。"
这里的二进制兼容性是什么意思?
能否有人给我指导一个 Java 示例,以展示代码是二进制兼容的?

http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html - Ray Cheng
1
考虑破坏二进制兼容性变化时的必备参考:https://wiki.eclipse.org/Evolving_Java-based_APIs_2 - jordanpg
3个回答

60

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

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项),由于相同的原因,这也会破坏二进制兼容性。

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


11
让我感到惊奇的是,官方文件无法用这种简单明了的语言来解释他们的内容。我不确定为什么他们要使用律师通常在条款和协议中使用的类似语言。 - Tarik
8
Richard E. Little在他的博客上有一个非常简单、易懂的例子。它充分展示了二进制兼容性的问题,而不是源代码兼容性,正如这个答案中所示(该方法已被重命名)。 - Yann-Gaël Guéhéneuc
1
@Tarik,你尝试过哪些官方文档才会做出如此大胆的陈述? - Holger

37

为了更好地理解这个概念,有趣的是看到二进制兼容性并不意味着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指令:getstaticgetfield。这种情况被提到在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 {
    /** 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,第二次调用会抛出异常,但第一次不会。这是因为我们以Java无法在编译时检查的方式改变了method的合约:在此之前,它可以接受null参数,现在不行了。

注意:

  • Eclipse Wiki是一个很好的资源:https://wiki.eclipse.org/Evolving_Java-based_APIs
  • 接受null值的API是一个值得质疑的做法
  • 更改方法的内部逻辑比更改API兼容性的二进制代码要容易得多,因为方法的内部逻辑很容易更改

C 二进制兼容性示例

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


2
在我看来,第一个例子是不正确的,因为你删除了“static”,这导致API不兼容(即,字节码中的“Lib.i”不再起作用)。对于某些代码来说,推断API兼容性并不取决于客户端是否使用此API,而是取决于API兼容性的标准(或规范)。尽管我同意API兼容性并不意味着二进制兼容性。 - floating cat

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

        }
    }

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


默认方法和静态方法在Java 8或更高版本中可用。 - BIPIN SHARMA

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接