【问题标题】:Can I recompile a public API with a sub-interface and keep binary compatibility?我可以使用子接口重新编译公共 API 并保持二进制兼容性吗?
【发布时间】:2017-04-19 10:44:20
【问题描述】:

我有一个公共 API,在多个项目中多次使用:

public interface Process<C extends ProcessExecutionContext> {

    Future<?> performAsync(C context);

}

还有一个负责实现 Future 机制的抽象类(未显示)。我知道 all 项目子类化了相应的抽象类(其 performAsync 是 final),并且没有一个类在没有抽象实现者子类的情况下实现抽象接口。这是设计使然,因为这个“公共”API 在我们公司内部是“公共的”。

发现 Future 与 Spring 的 ListenableFuture 相比限制太大,我决定将接口扩展到

public interface Process<C extends ProcessExecutionContext> {

    ListenableFuture<?> performAsync(C context);

}

我已经在示例中未显示的单个抽象超类中实现了 ListenableFuture。根据设计,不存在其他实现。

到目前为止,每个调用者都使用Future,它是ListenableFuture 的超级接口。如果你使用Future&lt;?&gt; future = processReturningListenable.performAsync(context),代码编译得很好。

问题是:如果我部署公共 API 的最新 JAR,其中包含 both 接口和抽象超类与 ListenableFuture 实现到现有环境,无需重新编译所有项目performAsync 调用仍然有效吗?

即当接口被替换为返回原始类型的子类型的方法时,Java 是否授予接口的二进制兼容性?

我问这个是因为 1) 我发现没有人可以使用现有 JAR 文件进行简单测试,并且 2) 必须重新编译所有项目是一个红色警报。

我假设我的要求是可能的,因为 Java 方法名称由计算方法名称和输入参数的签名标识。更改输出参数不会更改方法的名称

【问题讨论】:

  • 我不希望它在一个 jar 文件中产生任何影响 - 所以你应该能够只用一个小应用程序(一个接口,一个实现,一个调用者;重新编译接口+实现,但不是调用者,看看它是否仍然有效)。
  • @Jon Skeet:不需要测试;那样不行。

标签: java oop binary-compatibility jvm-bytecode


【解决方案1】:

这已在The Java® Language Specification, §13. Binary Compatibility, §13.4.15. Method Result Type 中直接解决:

更改方法的结果类型,或将结果类型替换为void,或将void 替换为结果类型,具有删除旧方法和添加具有新结果类型的新方法的组合效果或新的void 结果(见§13.4.12)。

引用的 §13.4.12 说:

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

所以答案是,不,如果不破坏与现有代码的二进制兼容性,你就不能这样做。

从技术上讲,假设方法仅由名称和参数类型标识是完全错误的,在字节码级别上,它们始终由名称、参数类型返回类型标识。

但请注意,上面的引用指出“只有在超类中没有声明具有匹配签名和返回类型的方法时才会发生这样的错误”。这指导了一个可能的解决方法:

interface LegacyProcess<C extends ProcessExecutionContext> {
    Future<?> performAsync(C context);
}
public interface Process<C extends ProcessExecutionContext> extends LegacyProcess<C> {
    @Override ListenableFuture<?> performAsync(C context);
}

现在,ProcessLegacyProcess 继承了一个匹配方法,这是一个不需要导出的类型,然后根据您的需要用更具体的返回类型覆盖它。这称为“协变返回类型”。在字节码级别,将有一个“Future performAsync(…)”方法委托给实际的实现方法“ListenableFuture performAsync(…)”。这种自动生成的委托方法称为桥接方法

这样,现有的已编译客户端代码将继续工作,而每个重新编译的代码将直接开始使用新方法,而无需桥接方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-05-01
    • 1970-01-01
    • 2012-04-18
    • 2011-02-05
    • 2011-10-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多