Virtual extension methods

Overview

Until now, it was not possible to add new methods to an existing interface without breaking backwards compatibility. When adding a new method to an interface, the new method would have to be implemented by all classes which implemented this interface. This made it basically impossible to add new methods to Java runtime classes such as java.util.List.

Especially with the Lambda expressions, it became necessary to add new methods to interfaces from the Collection API, so that Collections can be used together with Lambda expressions. One example is the java.util.Iterator interface: it now contains a new method

void forEach(Consumer<? super T> action)

which allows to use a Lambda expression as the iterator function.

In order to make it possible to add new methods to existing interfaces, Java 8 introduced the Virtual Extension Methods. This simply means, interfaces can now implement methods. This is very different from interfaces so far: until Java 7, an interface could not contain method bodies.

Syntax

Lets consider a real example, given the following interface and a simple implementing class:

  interface Printer {
    void printString(String value);
  }

  class DefaultPrinter implements Printer {

     @Override
     public void printString(String value) {
        System.err.println(value);
     }
  }

Lets add a new method to the interface to print an int value:

interface Printer {
  void printString(String value);
  void printInt(int value);
}

The compiler reports, as expected, that the DefaultPrinter class needs to implement this new method. So, in a real life example, we would need to implement the new method in all classes which implement the Printer interface. With Java 8, we can instead simply add a default implementation into the interface:

interface Printer {
  void printString(String value);

  default void printInt(int value) {
    System.err.println("int: " + value);
  }
}

Now, our original DefaultPrinter class still compiles, without errors.

Conflicting default methods

One question which everyone tends to ask now is: what about multiple inheritance? As we know, in Java, a class can only inherit from one super class, but it can implement multiple interfaces. This was not an issue so far, since interfaces only contained the method signatures, but no implementations. Now, with Java 8, a class could implement two interfaces which provide implementations for the same method signature:

interface Printer {
  void printString(String value);

  default void printInt(int value) {
    System.err.println("int: " + value);
  }
}

interface LogPrinter {
  void printString(String value);

  default void printInt(int value) {
    try {
       PrintWriter pw = new PrintWriter(new FileOutputStream("out.log"));
       pw.println("int: " + value);
    } catch(IOException e) {
       e.printStackTrace();
    }
  }
}

class DefaultPrinter implements Printer, LogPrinter {

   @Override
   public void printString(String value) {
      System.err.println(value);
   }
}

DefaultPrinter implements both Printer and LogPrinter and would inherit the method implementation of printInt() from both interfaces. Actually, this is not possible and the compiler refuses to compile it with an error message:

VirtualExtensionMethods.java:31: error: 
class DefaultPrinter inherits unrelated defaults for printInt(int) 
from types Printer and LogWriter

So, in that case, we either need to change our interfaces so that the methods have different names or at least different parameters, or we need to modify the implementing class to resolve the conflict by implementing its own version of the conflicting method. Besides implementing a totally different method, it is also possible to delegate to one of the conflicting default methods from the interfaces through super:

class DefaultPrinter implements Printer, LogWriter {

  ...

  @Override
  // pick printInt from Printer interface
  public void printInt(int value) { Printer.super.printInt(value);  } 
}

Conclusion

Virtual Extension Methods allow to add new methods to existing interfaces without breaking backwards compatibility. However, they should be restricted to exactly these scenarios where it is necessary to add new methods to existing interfaces. Otherwise, if some methods shall contain a default implementation, use abstract classes.