UPDATE: Since releasing this post, we’ve published a full Rebel Labs report entitled Java 8 Revealed: Lambdas, Default Methods and Bulk Data Operations. Click on the button below to get the full report!
Get the complete Rebel Labs report
In a previous post, we looked into lambdas in Java 8 which proved to be a very popular topic. Now we continue our peek into new Java 8 features by continuing with default methods. Those are closely related to lambdas, which could be the main theme of Java 8. In this article, we’ll take a look at what default methods are, what are the gotchas in using the default methods and how to apply the new feature in your daily life.
Why default methods?
Java 8 is approaching and even though the deadline has been pushed back, we can be quite confident that it will bring lambdas when it is finally out. As stated above, we did already cover the subject a bit some time ago, however, lambdas alone are not the only game-changer in Java 8.
Suppose Java 8 is out and has lambdas. Now you would like to start using lambdas and the most obvious use case for that is to apply a lambda to every element of a collection.
forEach isn’t declared by
java.util.List nor the
java.util.Collection interface yet. One obvious solution would be to just add the new method to the existing interface and provide the implementation where required in the JDK. However, once published, it is impossible to add methods to an interface without breaking the existing implementation.
So it’d be really frustrating if we had lambdas in Java 8 but couldn’t use those with the standard collections library since backwards compatibility can’t be sacrificed.
Due to the problem described above a new concept was introduced. Virtual extension methods, or, as they are often called, defender methods, can now be added to interfaces providing a default implementation of the declared behavior.
Simply speaking, interfaces in Java can now implement methods. The benefit that default methods bring is that now it’s possible to add a new default method to the interface and it doesn’t break the implementations.
In my opinion, it doesn’t seem to be the language feature that would be appropriate to use every day, but it seems to be essential for Java Collections API update to be able to use lambdas naturally.
The most trivial example
Let’s start with the simplest example possible: an
interface A, and a
class Clazz that implements
The code compiles even though Clazz does not implement method
foo() default implementation is now provided by
And the client code that users the example:
There is one common question that people ask about default methods when they hear about the new feature for the first time: “What if the class implements two interfaces and both those interfaces define a default method with the same signature?”. Let’s use the previous example to illustrate this situation:
This code fails to compile with the following result:
java: class Clazz inherits unrelated defaults for foo() from types A and B
To fix that, in Clazz, we have to resolve it manually by overriding the conflicting method:
But what if we would like to call the default implementation of method
interface A instead of implementing our own. It is possible to refer to refer to
A#foo() as follows:
Now I’m not quite sure that I like the final solution. Maybe it would be more elegant to specify the default method implementation in the signature, as it was specified in the first drafts of the default methods specification:
But it actually changes the grammar, doesn’t it? It looks more like a method declaration of an interface rather than the implementation form. But what if
interface A and
interface B define a lot of conflicting default methods and I’d like to resolve all the default methods from
interface A? Currently, I’d have to resolve the conflicts one by one, overriding each conflicting method pair. That might be a lot of work and a lot of boilerplate code to write.
I guess there could be a lot of arguments for and against this way of resolving the conflicts but it seems that the creators just decided to accept the necessary evil.
The real examples of the the default method implementations can be found in the early builds of JDK8. Going back to the example of
forEach method for collections, we can find its default implementation in
forEach method takes
java.util.function.Consumer functional interface type as a parameter which enables us to pass in a lambda or a method reference as follows:
Let’s take a look on how the default methods are actually invoked. If you are not familiar with the subject, the Rebel Labs report on Java bytecode might be an interesting read for you.
From the client code perspective, default methods are just ordinary virtual methods. Hence the name – virtual extension methods. So in case of the simple example with one class that implements an interface with a default method, the client code that invokes the default method will generate
invokeinterface at the call site.
In case of the default methods conflict resolution, when we override the default method and would like to delegate the invocation to one of the interfaces the
invokespecial is inferred as we would call the implementation specifically:
Here’s the javap output:
public void foo();
1: invokespecial #2 // InterfaceMethod A.foo:()V
As you can see, invokespecial instruction is used to invoke the interface method
foo(). This is also something new from the bytecode point of view as previously you would only invoke methods via super that points to a class (parent class), and not to an interface.
Default methods are an interesting addition to the Java language–you can think of them as a bridge between lambdas and JDK libraries. The primary goal of default methods is to enable an evolution of standard JDK interfaces and provide a smooth experience when we finally start using lambdas in Java 8. Who knows, maybe in the future we will see some more applications of default methods for API design.