简体   繁体   English

Java二进制兼容性 - 使用invokevirtual语义提出的协变返回类型解决方案的RFC

[英]Java binary compatibility - RFC on proposed solution to covariant return type using invokevirtual semantics

I'm trying to evolve an API. 我正在尝试改进API。 As part of this evolution I need to change the return type of a method to a subclass (specialize) in order for advanced clients to be able to access the new functionality. 作为此演变的一部分,我需要将方法的返回类型更改为子类(specialize),以便高级客户端能够访问新功能。 Example (ignore the ugly : 示例(忽略丑陋:

public interface Entity {
  boolean a();
}

public interface Intf1 {
  Entity entity();
}

public interface Main {
  Intf1 intf();
}

I now want to have ExtendedEntity, Intf2 and Main like this: 我现在想要像这样:ExtendedEntity,Intf2和Main:

public interface ExtendedEntity extends Entity {
  boolean b();
}

public interface Intf2 extends Intf1 {
  ExtendedEntity entity();
}

public interface Main {
  Intf2 intf();
}

However, since method return type is part of it's signature, clients already compiled with the previous version of the code show linkage errors (method not found iirc). 但是,由于方法返回类型是其签名的一部分,因此已使用先前版本的代码编译的客户端显示链接错误(未找到方法iirc)。

What I would like to do is add a method to Main with a different return type. 我想要做的是向Main添加一个具有不同返回类型的方法。 The two methods (one that return super type and one that return subtype) should be mapped to the same implementation method (which returns the subtype). 这两个方法(一个返回超类型,一个返回子类型)应该映射到相同的实现方法(返回子类型)。 Note - as far as I understand this is allowed by the JVM, but not by the Java spec. 注意 - 据我所知,这是JVM允许的,但不是Java规范。

My solution, which seems to work abuses (I have no other word for that) the Java class system to add the required interface. 我的解决方案, 似乎工作滥用(我没有其他的说法)Java类系统添加所需的接口。

public interface Main_Backward_Compatible {
  Intf1 intf();
}

public interface Main extends Main_Backward_Compatible{
  Intf2 intf();
}

Now old clients will have the correct method returned to the invokevirtual lookup (since the method with the correct return type exists in the type hierarchy) and the implementation that will actually work will be the one that returns the subtype Intf2. 现在,旧客户端将返回到invokevirtual查找的正确方法(因为类型层次结构中存在具有正确返回类型的方法),实际工作的实现将是返回子类型Intf2的实现。

This seems to work. 似乎有效。 In all the tests I could devise (barring reflection - but I don't care about that bit) it did work. 在我可以设计的所有测试中(除了反思 - 但我不关心那一点)它确实有效。
Will it always work? 它会一直有效吗? Is my reasoning (about the invokevirtual) correct? 我的推理(关于invokevirtual)是正确的吗?

And another, related, question - are there tools to check "real" binary compatibility? 另一个相关的问题 - 是否有工具来检查“真正的”二进制兼容性? The only ones I've found look at each method by itself, but fail to consider type hierarchy. 我发现的唯一一个单独查看每个方法,但没有考虑类型层次结构。

Thanks, 谢谢,
Ran. 然。

Edit - Tools I've tried and found "not so good" (do not take into account type hierarchy): 编辑 - 我尝试过的工具,发现“不太好”(不考虑类型层次结构):

  1. Clirr 0.6. Clirr 0.6。
  2. IntelliJ "APIComparator" plugin. IntelliJ“APIComparator”插件。

Edit2 - Of course, my clients are barred from creating implementation classes to my interfaces (think services). Edit2 - 当然,我的客户被禁止为我的接口创建实现类(想想服务)。 However, if you want the example to be complete, think abstract class (for Main) instead of interface. 但是,如果您希望示例完整,请考虑抽象类(对于Main)而不是接口。

This was long enough that I admit I didn't read everything scrupulously, but it seems like you might actually want to leverage generics here. 这足够长,我承认我没有仔细阅读所有内容,但似乎你可能真的想在这里利用泛型。 If you type Intf1 I think you can maintain binary compatibility while introducing specializations: 如果您键入Intf1我认为您可以在引入特化时保持二进制兼容性:

public interface Intf1<T extends Entity> {
  T entity(); //erasure is still Entity so binary compatibility
}

public interface Intf2 extends Intf1<ExtendedEntity> { //if even needed
}

public interface Main {
  Intf1<ExtendedEntity> intf(); //erasure is still Intf1, the raw type
}

Edit #1: There are some caveats when trying to maintain binary compatibility. 编辑#1:尝试维护二进制兼容性时有一些注意事项。 See the Generics Tutorial chapters 6 and 10 for more information. 有关详细信息,请参阅泛型教程第6章和第10章。

Edit #2: 编辑#2:

You can extend this concept to typing Main as well: 您可以将此概念扩展为键入Main

public interface Main<T, I extends Intf1<T>> {
    I intf(); //still has the same erasure as it used to, so binary compatible
}

Old clients would then be able to use the raw Main type as they used to with no recompilation needed, and new clients would type their references to Main: 然后,旧客户端可以像以前一样使用原始Main类型而无需重新编译,新客户端将键入对Main的引用:

Main<ExtendedEntity, Intf2> myMain = Factory.getMeAMain();
Intf2 intf = myMain.intf();

我们最终不需要解决方案,但事实证明它在此之前工作。

It would be simpler not to change the existing interfaces at all. 根本不更改现有接口会更简单。 Anyone using your new interface will be writing new code anyway. 任何使用新界面的人都会编写新代码。

Implementations of the existing Main.intf() signature can return an instance of Intf2. 现有Main.intf()签名的实现可以返回Intf2的实例。

Optionally, you could provide a new accessor that does not require casting: 或者,您可以提供不需要强制转换的新访问者:

public interface Main2 extends Main {
  Intf2 intf2();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM