是否使返回类型具有相同的擦除二进制兼容性?

时间:2017-02-11 08:56:37

标签: java generics backwards-compatibility jls

我有以下课程:

public abstract Foo {
  Foo() {}

  public abstract Foo doSomething();

  public static Foo create() {
    return new SomePrivateSubclassOfFoo();
  }
}

我想将其更改为以下定义:

public abstract Foo<T extends Foo<T>> {
  Foo() {}

  public abstract T doSomething();

  public static Foo<?> create() {
    return new SomePrivateSubclassOfFoo();
  }
}

此更改二进制兼容吗? 即,针对旧版本的类编译的代码是否可以使用新版本而无需重新复制?

我知道我需要更改SomePrivateSubclassOfFoo,这没关系。我也知道,当编译旧客户端代码时,此更改将触发有关原始类型的警告,这对我也是可以的。我只是想确保不需要重新编译旧的客户端代码。

根据我的理解,这应该没问题,因为T的删除是Foo,因此字节代码中doSomething的签名与之前相同。如果我查看由javap -s打印的内部类型签名,我确实看到了这一点(尽管在没有-s的情况下打印的&#34;非内部&#34;类型签名确实不同)。 我也测试了这个,它对我有用。

但是,Java API Compliance Checker告诉我这两个版本不是二进制兼容的。

那么什么是正确的? JLS是否保证二进制兼容性,或者我在测试中是否幸运? (为什么会发生这种情况?)

1 个答案:

答案 0 :(得分:4)

是的,您的代码似乎不会破坏二进制兼容性。
一些爬行/阅读后我发现了这些http://docs.oracle.com/javase/specs/jls/se8/html/jls-13.html#jls-13.4.5 其中说: -

  

添加或删除类的类型参数本身不会对二进制兼容性产生任何影响。   
  ...   
  更改类的类型参数的第一个边界可能会更改在其自己的类型中使用该类型参数的任何成员的擦除(第4.6节),这可能会影响二进制兼容性。这种界限的变化类似于方法或构造函数的类型参数的第一个边界的变化(§13.4.13)。

http://wiki.eclipse.org/Evolving_Java-based_APIs_2#Turning_non-generic_types_and_methods_into_generic_ones进一步澄清: -

  

根据特殊兼容性故事,Java编译器将原始类型视为对类型擦除的引用。通过向类型声明添加类型参数并明智地将类型变量的使用引入其现有方法和字段的签名中,可以将现有类型演变为泛型类型。只要擦除在泛化之前看起来像相应的声明,该更改就与现有代码二进制兼容。

所以你现在没有问题,因为这是你第一次将这个课程弄清楚。

但请记住,因为上述文档也说: -

  

但是,还要记住,对于类型参数(参见上表),如何对已经通用的类型或方法进行兼容演化存在严重的限制。因此,如果您计划生成API,请记住您只有一次机会(发布),以使其正确。特别是,如果您将API签名中的类型从原始类型“List”更改为“List&lt;?&gt;”或者“List&lt; Object&gt;”,您将被锁定在该决定中。道德观点认为,对API进行整合应该从API整体的角度考虑,而不是逐个方法或逐个类地逐步考虑。

所以我认为,第一次做这个改变是好的,但你只有一次机会才能充分利用它!