【问题标题】:Can I change a return type to be a strict subtype and retain binary compatibility?我可以将返回类型更改为严格的子类型并保持二进制兼容性吗?
【发布时间】:2017-11-24 15:46:58
【问题描述】:

如果我有一些课程:

import java.util.Date;
public final class Foo {
    private Date date;
    public Date getDate(){ return date; }
}

如果我已将其编译为二进制文件并且其他人已针对它构建了代码,我可以随后在不破坏二进制兼容性的情况下执行此操作吗?

import java.sql.Date;
public final class Foo {
    private Date date;
    public Date getDate(){ return date; }
}

注意java.sql.Datejava.util.Date 的子类。

在我看来很明显,如果我没有将类声明为 final,那么我将破坏源代码兼容性(即,以前有人可能已经针对我的库编译了 Foo 的子类,它覆盖了 getDate 方法以返回java.util.Date;该代码将不再针对我的最新版本编译)。但是破坏源代码兼容性是否意味着二进制兼容性也被破坏了? (这在其他语言中不成立,例如 scala)

【问题讨论】:

  • 你可以运行这个compatibility checker tool看看你是否这样做。
  • 您需要什么样的二进制兼容性?编译了什么,替换了什么?以及如何加载它?

标签: java binary-compatibility


【解决方案1】:

我对此进行了测试。

不幸的是,当方法的返回类型更改为原始类型的子类型时,调用时会出现NoSuchMethodError 异常。

因此,进行此更改不是二进制向后兼容的。

这太糟糕了……

测试设置

test
├── c
├── c_int
│   └── C.java
├── c_num
│   └── C.java
└── Test.java

test/c_num/C.java:

package test.c;

public class C {
    public Number f() {
        System.out.println("f Number");
        return 0.0;
    }
}

test/c_int/C.java:

package test.c;

public class C {
    public Integer f() {
        System.out.println("f Integer");
        return 1;
    }
}

test/Test.java:

package test;

import test.c.C;

public class Test {
    public static void main(String[] args) throws Exception {
        C b = new C();
        Number n = b.f();
        System.out.println(n);
    }
}

测试

编译两个返回类型不同的C类:

$ javac test/c_int/C.java
$ javac test/c_num/C.java

针对C 编译Test,返回类型为Number

$ cp test/c_num/C.class test/c/
$ javac test/Test.java

针对C 运行Test,返回类型为Number

$ java test.Test
f Number
0.0

C 运行Test,返回类型为Integer,无需重新编译Test

$ cp test/c_int/C.class test/c/
$ java test.Test
Exception in thread "main" java.lang.NoSuchMethodError: 'java.lang.Number test.c.C.f()'
    at test.Test.main(Test.java:8)

字节码

我们还可以在字节码中看到f的方法调用包含了方法的返回类型:

$ javap -c test/Test.class
Compiled from "Test.java"
public class test.Test {
  public static void main(java.lang.String[]);
    Code:
       ...
       9: invokevirtual #4                  // Method test/c/C.f:()Ljava/lang/Number;
       ...
}

Eclipse 文档

Eclipse 基金会维护一个名为Evolving Java-based APIs 的文档。

文档说明了二进制兼容性:

Evolving API classes - API methods and constructors
....
Change result type (including void)     -   Breaks compatibility

这不会对仅缩小返回类型的更改进行例外处理。对返回类型的所有更改都会破坏二进制兼容性。

【讨论】:

    【解决方案2】:

    我认为最好的方法是自己测试。这是官方documentation的一些sn-p:

    13.4.15。方法结果类型

    更改方法的结果类型,或将结果类型替换为 void 或将 void 替换为结果类型,具有以下综合效果 删除旧方法并使用新结果添加新方法 类型或新的无效结果(参见 §13.4.12)。

    13.4.12。方法和构造函数声明

    从类中删除方法或构造函数可能会破坏兼容性 与引用此方法的任何预先存在的二进制文件或 构造函数;当这样的引用时,可能会抛出 NoSuchMethodError 来自预先存在的二进制文件是链接的。只有在以下情况下才会发生此类错误 没有在 a 中声明具有匹配签名和返回类型的方法 超类。

    我的阅读方式是,如果您正在更改返回类型,无论它是否是旧类型的子类型,都意味着您正在删除旧方法并添加新方法,并且根据 13.4.12 它会破坏兼容性 与引用此方法或构造函数的任何预先存在的二进制文件。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-08
      • 2018-07-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多