我可以用子接口重新编译 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);
}
现在,Process
从 LegacyProcess
继承了一个匹配方法,一个不需要导出的类型,然后用更具体的 return 类型覆盖它,就像你希望。这称为“共变 return 类型”。在字节码层面,会有一个“Future performAsync(…)
”方法委托给实际的实现方法“ListenableFuture performAsync(…)
”。这种自动生成的委托方法被称为桥接方法。
这样,现有编译的客户端代码将继续工作,而每个重新编译的代码将开始直接使用新方法,而无需桥接方法。
我有一个 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 replacingvoid
with a result type, has the combined effect of deleting the old method and adding a new method with the new result type or newlyvoid
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);
}
现在,Process
从 LegacyProcess
继承了一个匹配方法,一个不需要导出的类型,然后用更具体的 return 类型覆盖它,就像你希望。这称为“共变 return 类型”。在字节码层面,会有一个“Future performAsync(…)
”方法委托给实际的实现方法“ListenableFuture performAsync(…)
”。这种自动生成的委托方法被称为桥接方法。
这样,现有编译的客户端代码将继续工作,而每个重新编译的代码将开始直接使用新方法,而无需桥接方法。