Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is making return type generic with same erasure binary compatible?

I have the following class:

public abstract Foo {
  Foo() {}

  public abstract Foo doSomething();

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

I want to change it to the following definition:

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

  public abstract T doSomething();

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

Is this change binary compatible? I.e., will code that is compiled against the old version of the class work with the new version without reocmpilation?

I know that I need to change SomePrivateSubclassOfFoo, this is ok. I also know that this change will trigger warnings about raw types when old client code is compiled, this is also ok for me. I just want to make sure that old client code does not need to be recompiled.

From my understanding, this should be ok because the erasure of T is Foo, and thus the signature of doSomething in the byte code is the same as before. If I look at the internal type signatures printed by javap -s, I indeed see this confirmed (although the "non-internal" type signatures printed without -s do differ). I also did test this, and it worked for me.

However, the Java API Compliance Checker tells me that the two versions are not binary compatible.

So what is correct? Does the JLS guarantee binary compatibility here, or was I just lucky in my tests? (Why could this happen?)

like image 257
Philipp Wendler Avatar asked Feb 11 '17 08:02

Philipp Wendler


People also ask

What is the downside of type erasure?

The downside of type erasure is that you do not know at runtime the type of the generic. This implies that you cannot apply reflection on them and you cannot instantiate them at runtime.

How to return a value that is not type casteable?

You need pass a type that is type casteable for the value you return through that method. If you would want to return a value which is not type casteable to the generic type you pass, you might have to alter the code or make sure you pass a type that is casteable for the return value of method.

Why should I use erased generic functions?

Another reason to favor erased generics is because dependencies should be injected in a way that respects the solution to the Expression Problem. If you are testing for specific cases of types or hardcoding a factory in your generic function, then you are doing extensibility wrong.

How do I convert a value to a generic type?

Option 1: Straight approach - Create multiple functions for each type you expect rather than having one generic function. Option 2: When you don't want to use fancy methods of conversion - Cast the value to object and then to generic type. Note - This will throw an error if the cast is not valid (your case).


1 Answers

Well yes your code does not seem to break binary compatibility.
I found these after some crawling/reading
http://docs.oracle.com/javase/specs/jls/se8/html/jls-13.html#jls-13.4.5 which says:-

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).

And this http://wiki.eclipse.org/Evolving_Java-based_APIs_2#Turning_non-generic_types_and_methods_into_generic_ones further clarifies:-

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.

So you have no problems as of now since it is the first time you are generifying that class.

But please keep in mind as the above doc also says :-

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.

So I think, its alright to make this change for the very first time but you have one chance only so make full use of it!

like image 76
Shubham Chaurasia Avatar answered Sep 29 '22 15:09

Shubham Chaurasia