Java 8 virtual extension methods are begging to be free of interfaces
To cope with API evolution and to provide new extensions for lambda-friendly methods, Java 8 will introduce a form of extension methods in interfaces (which were called public defender methods in previous iterations of the proposal). Interface methods will be allowed to have bodies (or just a reference to a static method):
interface MyInterface {
void method() default {
System.out.println("A good idea?");
}
}
A class implementing MyInterface
doesn't need to provide an implementation for method()
. This is particularly valuable for interface evolution to maintain binary compatibility with classes already compiled with the old interface version. In general, the old binaries would work with the new defaulted interface method without recompilation; it will just use the default implementation.
The default
keyword is a deliberate choice not to conflict with the rule that "interfaces have no code". Now they can have default code.
Conflict resolution works by first looking for a method implementation in the class and all its superclasses (even if the method is abstract) and then looking for the more specific interface that provides the default method. If the conflict still persists, the code will throw a linker error (for already compiled code) or won't even compile.
I see two issues with this approach.
First, the decision to treat default methods as just last-resort code does not seem intuitive. A default method implementation won't even win over an abstract method declared in a superclass. In my view, concrete methods should always win over abstract methods in this situation, they should only lose if they're explicitly overridden by abstract methods in the implementing class itself.
Second, this choice is insufficient to cope with other implementation issues that might arise. For example, the usual extract method refactoring won't be possible for a default method because interfaces still won't allow private methods (as far as I know).
The second issue alone makes me question the design of the feature, or at least think that it's still incomplete.
What instead I'd like to see is another construct altogether, like a trait (or role). Traits would not conflict with the semantics of interfaces and would be free to incorporate more interesting options. The migration path from an interface to a trait should be easy: just change the keyword interface
to trait
and add the modifiers public abstract
to all the methods (IDEs could do this automatically). Then, the programmer could add methods with bodies to traits (the equivalent of the current proposal), but would also be able to create methods with other accessibility modifiers (or even final
). To not complicate the design too much, instance fields and constructors would still not be allowed:
trait MyTrait {
public void method() {
System.out.println("A good idea!");
}
// reuse an existing static method that takes the trait type as a first parameter
public void otherMethod() =
SomeClass.someCompatibleStaticMethod;
}
A class (or interface, or trait) would then use (or compose) a trait (or many), hopefully with a revised conflict resolution strategy (but not the one from Scala, which is too implicit):
class MyClass uses MyTrait {
}
interface MyInterface uses MyTrait {
}
Classes or interfaces implementing traits could generate a warning, but traits would be binary-compatible with their interface counterparts:
class MyClass
implements MyTrait { // warning: should use `uses` for trait
}
interface MyInterface
implements MyTrait { // warning: should use `uses` for trait
}
This design will not take more effort than the current proposal, but I think the change in focus from interfaces to traits would allow the language to evolve in more interesting ways in the future, and not be painted into a corner. Interfaces would be preserved as they are today, and its semantics would not get in the way anymore.
To have an idea of where the design could go, consider these possible features:
Traits could have instance fields, maybe only private ones to avoid difficult conflict situations.
trait PropertyBag {
private final Map<String, Object> bag = ...
public void set(String name, Object value) {
// ...
}
public Object get(String name) {
// ...
}
}
Since traits need to be composed into target classes, there might be a need to reference the type of those classes in the trait. A self-type would allow for that, in a kind of compiler-backed generic parameter that would be automatically provided for target classes:
trait PropertyBag self TSelf {
public TSelf with(String name, Object value) {
// ...
return this; // TSelf is the type of this
}
}
class MyClass
uses PropertyBag // self-type is MyClass
{
...
}
A construct that uses many traits could alias trait members to have more meaningful names in its context:
class MyClass uses PropertyBag {
public void setProperty(String name, Object value)
aliases PropertyBag.set;
public MyClass withProperty(String name, Object value)
aliases PropertyBag.with;
}
Traits could publish required traits that should be present in a target class that uses it:
trait MyTrait self (TSelf uses MyOtherTrait) {
...
}
Traits could even require interfaces and a specific class (or superclass) for the target class to conform to.
So, a simple change in focus changes the future outlook for the language. For more ideas and a powerful implementation of this concept, just look at Scala (which doesn't even have interfaces).
Comments
Post a Comment