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

时间:2010-08-17 06:53:35

标签: java compiler-construction binary-compatibility

我正在尝试改进API。作为此演变的一部分,我需要将方法的返回类型更改为子类(specialize),以便高级客户端能够访问新功能。 示例(忽略丑陋:

public interface Entity {
  boolean a();
}

public interface Intf1 {
  Entity entity();
}

public interface Main {
  Intf1 intf();
}

我现在想要像这样使用ExtendedEntity,Intf2和Main:

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

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

public interface Main {
  Intf2 intf();
}

但是,由于方法返回类型是其签名的一部分,因此已使用先前版本的代码编译的客户端显示链接错误(未找到方法iirc)。

我想要做的是向Main添加一个具有不同返回类型的方法。这两个方法(一个返回超类型,一个返回子类型)应该映射到相同的实现方法(返回子类型)。注意 - 据我所知,JVM允许这样做,但Java规范不允许这样做。

我的解决方案,似乎工作滥用(我没有其他说法)Java类系统添加所需的接口。

public interface Main_Backward_Compatible {
  Intf1 intf();
}

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

现在,旧客户端将返回到invokevirtual查找的正确方法(因为类型层次结构中存在具有正确返回类型的方法),实际工作的实现将是返回子类型Intf2的实现。

似乎可行。在我可以设计的所有测试中(除了反思 - 但我不关心那一点)它做了工作。
它会一直有效吗?我的推理(关于invokevirtual)是正确的吗?

另一个相关的问题 - 是否有检查“真正的”二进制兼容性的工具?我发现的唯一一个单独查看每个方法,但没有考虑类型层次结构。

谢谢,
冉。

编辑 - 我尝试过的工具,发现“不太好”(不考虑类型层次结构):

  1. Clirr 0.6。
  2. IntelliJ“APIComparator”插件。
  3. Edit2 - 当然,我的客户被禁止为我的界面创建实现类(想想服务)。但是,如果您希望示例完整,请考虑抽象类(对于Main)而不是接口。

3 个答案:

答案 0 :(得分:1)

这足够长,我承认我没有仔细阅读所有内容,但似乎你可能真的想在这里利用泛型。如果您键入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
}

编辑#1:尝试维护二进制兼容性时有一些注意事项。有关详细信息,请参阅Generics Tutorial第6章和第10章。

编辑#2:

您可以将此概念扩展为键入Main

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

然后,旧客户端可以像以前一样使用原始Main类型而无需重新编译,新客户端将键入对Main的引用:

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

答案 1 :(得分:1)

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

答案 2 :(得分:0)

根本不更改现有接口会更简单。任何使用新界面的人都会编写新代码。

现有Main.intf()签名的实现可以返回Intf2的实例。

或者,您可以提供不需要强制转换的新访问者:

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