3

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

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 的实现。

似乎有效。在我可以设计的所有测试中(除了反射-但我不在乎那一点)它确实有效。
它会一直有效吗?我的推理(关于调用虚拟)是否正确?

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

谢谢,
冉。

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

  1. 克利尔 0.6。
  2. IntelliJ“APIComparator”插件。

Edit2 - 当然,我的客户被禁止为我的接口创建实现类(想想服务)。但是,如果您希望示例完整,请考虑抽象类(用于 Main)而不是接口。

4

3 回答 3

1

我们最终不需要这个解决方案,但在此之前证明它是有效的。

于 2011-05-26T13:47:46.260 回答
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:尝试保持二进制兼容性时有一些注意事项。有关详细信息,请参阅泛型教程第 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();
于 2010-08-17T14:25:07.697 回答
0

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

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

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

public interface Main2 extends Main {
  Intf2 intf2();
}
于 2010-08-17T14:14:27.400 回答