使具有相同擦除二进制的 return 类型泛型兼容吗?

Is making return type generic with same erasure binary compatible?

我有以下 class:

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();
  }
}

此更改二进制兼容吗? 即,针对旧版本 class 编译的代码是否可以在不重新编译的情况下与新版本一起使用?

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

按照我的理解,这应该没问题,因为T的擦除是Foo,因此doSomething在字节码中的签名与之前相同。如果我查看 javap -s 打印的内部类型签名,我确实看到了这一点(尽管没有 -s 打印的 "non-internal" 类型签名确实不同)。 我也测试过这个,它对我有用。

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

那么什么是正确的呢? JLS 是否保证这里的二进制兼容性,或者我只是在测试中走运? (为什么会这样?)

是的,您的代码似乎没有破坏二进制兼容性。
我在 crawling/reading
http://docs.oracle.com/javase/specs/jls/se8/html/jls-13.html#jls-13.4.5 之后找到了这些 其中说:-

Adding or removing a type parameter of a class does not, in itself, have any implications for binary compatibility.
...
Changing the first bound of a type parameter of a class may change the erasure (§4.6) of any member that uses that type parameter in its own type, and this may affect binary compatibility. The change of such a bound is analogous to the change of the first bound of a type parameter of a method or constructor (§13.4.13).

而这个http://wiki.eclipse.org/Evolving_Java-based_APIs_2#Turning_non-generic_types_and_methods_into_generic_ones进一步阐明了:-

According to the special compatibility story, the Java compiler treats a raw type as a reference to the type's erasure. An existing type can be evolved into a generic type by adding type parameters to the type declaration and judiciously introducing uses of the type variables into the signatures of its existing methods and fields. As long as the erasure looks like the corresponding declaration prior to generification, the change is binary compatible with existing code.

所以你现在没有问题,因为这是你第一次生成 class。

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

But, also bear in mind that there are severe constraints on how a type or method that already is generic can be compatibly evolved with respect to its type parameters (see the tables above). So if you plan to generify an API, remember that you only get one chance (release), to get it right. In particular, if you change a type in an API signature from the raw type "List" to "List<?>" or "List<Object>", you will be locked into that decision. The moral is that generifying an existing API is something that should be considered from the perspective of the API as a whole rather than piecemeal on a method-by-method or class-by-class basis.

所以我觉得,第一次改还可以,但是只有一次机会,好好利用吧!