3

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

public interface Process<C extends ProcessExecutionContext> {

    Future<?> performAsync(C context);

}

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

与 Spring 相比,发现这Future太有限了,ListenableFuture我决定将接口扩展到

public interface Process<C extends ProcessExecutionContext> {

    ListenableFuture<?> performAsync(C context);

}

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

到目前为止,每个调用者都Future使用ListenableFuture. 如果您使用Future<?> future = processReturningListenable.performAsync(context).

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

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

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

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

4

1 回答 1

2

这已在Java® 语言规范第 13 节中直接解决。二进制兼容性,§13.4.15。方法结果类型

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

引用的§13.4.12 说:

…</p>

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

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

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

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

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这称为“协变返回类型”。在字节码级别,将有一个“<code>Future performAsync(…)”方法委托给实际的实现方法“<code>ListenableFuture performAsync(…)”。这种自动生成的委托方法称为桥接方法

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

于 2017-04-19T18:46:09.303 回答