Imagine a bacon-wrapped Ferrari. Still not better than our free technical reports.

Java 8 Revealed: Lambdas, Default Methods and Bulk Data Operations

Part 2: Default Methods

Once published, it is impossible to add methods to an interface without breaking the existing implementations. The purpose of default methods, introduced in Java 8, is to enable interfaces to evolve without introducing incompatibility with existing implementations.

Why default methods?

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.

List<?> list = ...
list.forEach(...); // lambda code goes here

The 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.

Your time is too valuable to sit around waiting for your code to redeploy. Seriously, imagine how much more you could accomplish if you choose to Eliminate Redeploys from your daily routine!

The default methods isn’t the language feature that would be appropriate to use every day, but it is an essential feature for Java Collections API update to be able to use lambdas naturally.

Defenders 101

Let’s start with the simplest example possible: an interface A, and a class Clazz that implements interface A.

public interface A {
  default void foo(){
     System.out.println("Calling A.foo()");
  }
}

public class Clazz implements A {
}

The code compiles even though Clazz does not implement method foo(). Method foo() default implementation is now provided by interface A.

And the client code that uses the example:

Clazz clazz = new Clazz();
clazz.foo(); // Calling A.foo()

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:

public interface A {
  default void foo(){
    System.out.println("Calling A.foo()");
  }
}

public interface B {
  default void foo(){
    System.out.println("Calling B.foo()");
  }
}

public class Clazz implements A, B {
}

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:

public class Clazz implements A, B {
    public void foo(){}
}

But what if we would like to call the default implementation of method foo() from interface A instead of implementing our own. It is possible to refer to refer to A#foo() as follows:

public class Clazz implements A, B {
  public void foo(){
    A.super.foo();
  }
}

The real examples of the the default method implementations can be found in JDK8. Going back to the example of forEach method for collections, we can find its default implementation in java.lang.Iterable interface:

@FunctionalInterface
public interface Iterable {
  Iterator iterator();

  default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
      action.accept(t);
    }
  }
}

The 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:

List<?> list = ...
list.forEach(System.out::println);

Download the PDF