Since Java 8 we can write implementations into interfaces. As several interfaces can be implemented by a class, a class can now inherit actual code (not just responsibilities) from multiple different sources. Such inherited default implementations are overridable, and multiple-inheritance problems will detected and rejected by the Java 8 compiler.
The interface Identified
demands the presence of an identifier like a database primary key.
The interface Named
demands the presence of a human readable name.
Objects of the example class Person
should be both identified and named.
public static void main(String[] args) {
Person person = new Person(1L, "John Doe");
System.out.println("Person id="+person.getId()+", name="+person.getName());
}
This should output:
Person id=1, name=John Doe
It was not possible to provide a default implementation of an interface responsibility, all implementations had to be in the class:
public interface Identified
{
Object getId();
}
public interface Named
{
String getName();
}
public class Person implements Identified, Named
{
private final Object identity;
private final String name;
public Person(Object identity, String name) {
this.identity = identity;
this.name = name;
}
@Override
public Object getId() {
return identity;
}
@Override
public String getName() {
return name;
}
}
In case the inherited interface responsibility happens to be optional, we can now provide a default implementation for any interface method:
public interface Named
{
default String getName() {
return "(undefined name)";
}
}
public interface Identified
{
default Object getId() {
return null;
}
}
As both interfaces provide defaults, the inheriting class is not required to implement anything:
public class Person implements Identified, Named
{
}
This compiles!
Output will be:
Person id=null, name=(undefined name)
Most important here: default
implementations can be overridden (other than static ones). In the Person
example this actually makes sense. Following source shows an implementation that redirects getId()
to the according constructor parameter:
public class Person implements Identified, Named
{
private final Object identity;
public Person(Object identity) {
this.identity = identity;
}
@Override
public Object getId() {
return identity;
}
}
The "diamond problem" occurs in case two inherited interfaces provide a default method of same signature:
public interface Identified
{
default boolean isDeleted() {
return false;
}
....
}
public interface Named
{
default boolean isDeleted() {
return true;
....
}
The isDeleted()
method has been introduced on both interfaces, with different default implementations. Which one would be used at runtime by Person
?
We don't need to think about this. Following error message is printed by the Java compiler when it comes to class Person
:
Duplicate default methods named isDeleted with the parameters () and () are inherited from the types Named and Identified.
Following override would fix it:
public class Person implements Identified, Named
{
....
@Override
public boolean isDeleted() {
return Identified.super.isDeleted();
}
}
The class decides for one of the defaults, in this case for the implementation in interface Identified
.
Interface default implementations could easily take over the role that static utility classes have been playing until now (StringUtils
, IOUtils
, ...). Theses classes provided "pure" method implementations that rely on parameters only, without using their enclosing class body in any way. The problem with these utils is that they are not overridable. Interface default methods are overridable and would solve this problem. Method signature clashes would be found by the compiler.
Keep in mind that interfaces still can not hold any state, that means default implementations need to be "pure", everything they need has to be passed to them as parameter. But an interface now is the perfect place for "pure" functions.
Mind further that interfaces methods are always public
, implicitly. You can not reduce their visibility to protected
or private
in an override.
ɔ⃝ Fritz Ritzberger, 2021-03-28