代码之家  ›  专栏  ›  技术社区  ›  Ran Biron

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

  •  3
  • Ran Biron  · 技术社区  · 15 年前

    我正在尝试开发一个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();
    }
    

    但是,由于方法返回类型是它的签名的一部分,因此已经用以前版本的代码编译的客户机将显示链接错误(methodnotfoundiirc)。

    我的解决方案 似乎

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

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

    这个 似乎 去工作。在所有我能设计的测试中(除了反射——但我不在乎那一点) 工作。

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

    谢谢,
    跑。

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

    1. IntelliJ“APIComparator”插件。

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

    3 回复  |  直到 15 年前
        1
  •  1
  •   Mark Peters    15 年前

    这已经足够长了,我承认我没有仔细阅读所有内容,但看起来你可能真的想在这里利用泛型。如果你打字 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
    }
    

    在尝试保持二进制兼容性时有一些注意事项。看到了吗 Generics Tutorial

    编辑#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();
    
        2
  •  1
  •   Ran Biron    15 年前

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

        3
  •  0
  •   Andy Thomas    15 年前

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

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

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