我可以使用子接口重新编译公共API并保持二进制兼容性吗?

时间:2017-04-19 10:44:21

标签: java oop binary-compatibility jvm-bytecode

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

public interface Process<C extends ProcessExecutionContext> {

    Future<?> performAsync(C context);

}

一个负责实现Future机制的抽象类(未显示)。我知道所有项目都是相应的抽象类的子类(performAsync是 final )并且没有单个类在没有子类化抽象实现者的情况下实现抽象接口。这是设计上的,因为这个&#34; public&#34; API是&#34; public&#34;我们公司内部。

发现Future与Spring ListenableFuture相比过于局限我决定将界面扩展到

public interface Process<C extends ProcessExecutionContext> {

    ListenableFuture<?> performAsync(C context);

}

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

到目前为止,每个调用者都使用Future,这是ListenableFuture的超接口。如果您使用Future<?> future = processReturningListenable.performAsync(context),则代码编译得很好。

问题是:如果我部署公共API的最新JAR,包含两者接口和带有ListenableFuture实现的抽象超类对于现有环境,而不重新编译所有项目performAsync调用是否仍然有效?

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

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

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

1 个答案:

答案 0 :(得分:2)

这已在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);
}

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

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