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

Can I recompile a public API with a sub-interface and keep binary compatibility?

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

public interface Process<C extends ProcessExecutionContext> {

    Future<?> performAsync(C context);

}

还有一个抽象 class 负责实现 Future 机制(未显示)。我知道 all 项目子 class 相应的抽象 class (performAsync 是 final)并且没有单个 class 实现了抽象接口而没有 subclass 抽象实现者。这是设计使然,因为这个 "public" API 在我们公司内 "public"。

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

public interface Process<C extends ProcessExecutionContext> {

    ListenableFuture<?> performAsync(C context);

}

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

到目前为止,每个调用者都使用 Future,它是 ListenableFuture 的超级接口。如果您使用 Future<?> future = processReturningListenable.performAsync(context).

,代码编译得很好

问题 是:如果我部署 public API 的最新 JAR,其中包含 both 接口和抽象 superclass 与 ListenableFuture 对现有环境的实现,无需重新编译所有项目performAsync 仍然调用工作?

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

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

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

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

Changing the result type of a method, or replacing a result type with void, or replacing void with a result type, has the combined effect of deleting the old method and adding a new method with the new result type or newly void result (see §13.4.12).

引用的§13.4.12 说法:

Deleting a method or constructor from a class may break compatibility with any pre-existing binary that referenced this method or constructor; a NoSuchMethodError may be thrown when such a reference from a pre-existing binary is linked. Such an error will occur only if no method with a matching signature and return type is declared in a superclass.

所以答案是,不,你不能在不破坏与现有代码的二进制兼容性的情况下这样做。

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

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

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

现在,ProcessLegacyProcess 继承了一个匹配方法,一个不需要导出的类型,然后用更具体的 return 类型覆盖它,就像你希望。这称为“共变 return 类型”。在字节码层面,会有一个“Future performAsync(…)”方法委托给实际的实现方法“ListenableFuture performAsync(…)”。这种自动生成的委托方法被称为桥接方法

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