Interface evolution via virtual extension methods - Extending Interfaces By Breaking Application Logic (Dangerous)
Brian Goetz
brian.goetz at oracle.com
Sun Mar 11 21:51:35 PDT 2012
Thanks for your comments. Responses inline:
> 1 - There is a risk of breaking client code: consider a developer who has already implemented a comparator like this: abstract class MyComparator implements Comparator { protected int order = 1; public void reverseOrder() { return -1*order;} }
> and uses this somewhere in his code like this: MyComparator c = new MyComparator() { public int compare(Objct o1, Object o2) { return order* (o1.toString().compareTo(o2.toString())); }} .... c.reverseOrder(); Arrays.sort(list, c); If oracle developers decide to add such a method in Comparator interface then they can not ensure that client code will not break; two methods with the same signature but with a different returned type can not compile (at least not in java 7).
Yes, this is a risk. Note that this is not new with extension methods; this risk exists for adding new methods to existing classes (abstract or concrete) too.
The alternative is: never add any new methods to libraries. We find this alternative unappealing!
> 2 - There is a risk of breaking client logic: according to Brian's paper, section 3. Method Dispatch, we can have a diamond-shaped hierarchy as:
> interface A { void m() default X.a; } interface B extends A { } interface C extends A { } class D implements B, C { }
> When the compiler sees d.m() and if there is not an implementation of m() in class D then it will call the default method implementation A.m().
The compiler does *not* short-circuit calls to D.m() to A.m(). The compiler emits an invokespecial with D.m(); the runtime will perform the resolution, and if the situation is still like this by the time the classes are actually loaded, then the VM will decide to dispatch to A.m(). (This is no different than today; the compiler's job is simply to sanity-check linkage and "fail fast" if the compiler can see the call would not succeed if made at runtime with the classes as they are at compile time, but the runtime builds vtables based on the classes it sees at runtime.)
> The client of this API can easily "make the mistake" of relying on default implementation of A.m().
Again, this is nothing new. Let's say class C has a method q(), and D extends C but does not override q(). Clients of D could easily "make the mistake" of assuming calls to D.q() are going to end up at C.q(), but that is simply a mistake.
> For some reason in the future the developerof the API would like to add a default implementation of m() in B. The problem in this case is bigger than in case 1;
> a) if the client of API have already extended interface A with B1 and provided a default implementation for m, and has a "class E implements B, B1" the client code will break.
At this point E will not compile, due to competing implementations in B and B1.
> b) the default m() in B will probably break invariants of a "class MyClass implements B, C" and cause no compile error.
Why? If B.m() overrides A.m(), we expect B.m() to implement the contract of A.m(). Note that interfaces have no state (barring heroic actions), so the protection of state-based invariants still falls entirely to C and its subclasses. The implementation of m() in A or B can only call other interface methods, and if any of those affect state in C, eventually we'll be calling code in C to actually touch the state.
> 1 - How to keep backward compatibility? Well at least in the transition phase from java 7 to java 8 nothing can ensure the java developers that adding a virtual method inCollection interface will be compatible with the old java. So my suggestion is another keyword, let say override. Let say we add reverseOrder() in interface Comparator.In this case the java compiler will make the reverseOrder() visible to Comparator's subtypes only and only if the subtype explicitly define: override reverseOrder().
I believe this is a request for "opting in" to extension methods, rather than having them actually inherited? I invite you to propose exactly how this might work in more detail.
> So incase 1 MyComparator (the client code) is not affected; reverseOrder() is not visible in MyComparator. 2 - How to avoid making API fragile by default (case 2)? My suggestion is the same as the above: the keyword override. By writing override reverseOrder() the API's clientis required to explicitly define which default implementation he is relying on, specifying SuperType.super.m() or make a "default none" in case of an interface. If the clientdoesn't specify override, there i!
You say "client" when I think you mean "subclass". Are you really suggesting the opt-in should be at the *client* site, or are you speaking only at the inheritance site?
More information about the lambda-dev
mailing list