Interface evolution via virtual extension methods - Extending Interfaces By Breaking Application Logic (Dangerous)

Elvis Ligu elvis_ligu at hotmail.com
Sun Mar 11 19:21:21 PDT 2012





Hello to everyone,
I was reading about lambda project and I was looking at a paper by Brian Goetz which is named: Interface evolution via virtual extension methods, founded at:http://cr.openjdk.java.net/~briangoetz/lambda/Defender%20Methods%20v4.pdf.
(I am not a native English speaker so I will try to express myself through some very simple examples about my thoughts)
Reading about virtual extension methods I found it very exciting the idea of extending an interface by a virtual method while maintaining backward compatibility.When you publish an interface to the public there is a big maintenance problem: the interface can not be changed (adding, removing, altering methods), at leastnot without breaking client's code. 
I understand why interface extension by virtual methods is so critical to the project lambda and having such code:
     people.sort(comparing(Person::getLastName).reverseOrder());
in java, this is very beautiful, concise and readable.Without virtual methods we can not see something like the above in java, reverseOrder() could not be added to Comparator interface.
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). 
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 client of this API can easily "make the mistake" of relying on default implementation of A.m(). 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. To make this worst imagine he uses B1 and E in other places in the code, a hole refactoring is required to be compatible with API. This is also the case 1.
b) the default m() in B will probably break invariants of a "class MyClass implements B, C" and cause no compile error. And in this case, depending on responsibilitiesmethod m() has, the problem can be very cryptic and difficult to locate. Consider the breaking of a thread safe class! Well the solution in this case is: Effective Java 3rd ed.where an item will be: don't ever rely on default implementation of a virtual method. If you do so, specify which class you rely on by typing: Interface.super.virtual();
Conclusions: 
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(). 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 is no problem at all, the virtual method will not be visible. In this case the client is forced to explicitly define where is he relying on, avoidingaccidental logic breaks.
Thank you for reading my thoughts. I hope you did make it through, reading my terrible English.
Elvis Ligu,Dept. of Applied InformaticsUniversity of Macedonia,Thessaloniki, Greece.
 		 	   		  


More information about the lambda-dev mailing list