Default Methods

Before getting into this article lets go through the Java API documentation of java.lang.Iterable interface version 7 and version 8.
The Java 1.7 Iterable interface has a single abstract method iterator() and the Java 1.8 Iterable interface has 3 methods.
According to the obsolete rule of Java 1.7 "Interfaces can have only abstract methods and must be overridden by the sub classes"; what will happen to the newly introduced methods, if they are abstract methods?
Assume a scenario where a software company ABC, developed a class Book which implements the java.lang.Iterable to iterate through its chapters.
class Book implements Iterable<Chapter> {
    private List<Chapter> chapters = new ArrayList<>();

    public void add(Chapter chapter) {
        this.chapters.add(chapter);
    }


    @Override
    public Iterator<Chapter> iterator() {
        return chapters.iterator();
    }
}
Complete code is available on Git Hub.

Assume that this library is in the market and there are billions of users using this library. Suppose if Java 8 introduced two more abstract methods in the Iterable interface and if some users upgrade their Java Runtime Environment to Java 8, what will happen? Definitely this Book class will fail to run since it does not have any implementations for those newly introduced abstract methods. However Java engineers did not make that mistake; always Java keeps its backward compatibility. This backward compatibility is achieved by introducing a different type of methods called default methods.

Default methods is one of the new features introduced in Java 8 which allows the developers to create methods in an interface with a body. The actual purpose of default methods as defined in Oracle documentation is:
"Default methods enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces."
Here is another example to explain the purpose of default methods even clear enough.
interface Sellable {
    double sell();
}

class Book implements Sellable {
    private double price;
    private double discount;

    public Book(double price, double discount) {
        this.price = price;
        this.discount = discount;
    }

    @Override
    public double sell() {
        return price - (price * discount / 100.00);
    }
}

class Pen implements Sellable {
    private double price;

    public Pen(double price) {
        this.price = price;
    }

    @Override
    public double sell() {
        return price;
    }
}
Complete code is available on Git Hub.

In the above example, the Sellable interface has a single abstract method sell() which is used to get the income by selling a single item. Assume that there are some classes which already implement this interface (Check the code here). After few versions of this interface, if you have decided to extend the functionality of this interface by adding a new method to sell more than one items, and if you go with abstract method then you need to modify the sub classes as well. Here the compatibility of your new version will be broken by the new abstract method. If you are using default method as shown below, then the problem is solved and you do not need to alter the sub classes.
interface Sellable {
    double sell();

    default double sell(int count) {
        double total = 0.0;
        for(int i = 1; i <= count; i++) {
            total += this.sell();
        }
        return total;
    }
}
Complete code is available on Git Hub.

Rule #1:
Default methods must have the modifier 'default' and a method body.

Rule #2:
Default methods can be defined only inside an interface not in any classes.
public class DefaultInClass {
 /*
 * Compile time error.
 * Cannot have default method inside a class.
 */
 default void doStuff() {
  System.out.println("Hello world");
 }
}

Rule #3:
Default methods cannot be used to override java.lang.Object class' methods.
public interface OverrideToString {
 /*
 * Compile time error.
 * Cannot override any methods of java.lang.Object class.
 */
 public default String toString() {
  return "Hello";
 }
}

Rule #4:
Sub classes or sub interfaces can override any default methods if they are interested. In fact as the name suggest default method means the default behavior of the specific interface. If sub classes or interfaces need to define their own implementation then they have the freedom to override the default behavior.
interface Readable {
 default void read() {
  System.out.println("Read");
 }
}

class Note implements Readable {
 private String content;
 public Note(String content) {
  this.content = content;
 }

 @Override
 public void read() {
  System.out.println(content);
 }
}

Rule #5:
'final' and 'default' modifiers cannot be used together, because the purpose of default methods is creating an interface with a default implementation and give the freedom to alter the behavior in sub classes or sub interfaces, if they are interested. If final is allowed then it will become a sealed interface and that is not the goal of interface evolution.
public interface FinalDefaultMethod {
 /*
 * Compile time error.
 * final and default cannot be used together.
 */
 public final default void print() {
  System.out.println("Hello world");
 }
}

Rule #6:
Since interfaces are stateless the 'synchronized' and 'default' modifiers cannot be used together.
public interface SynchronizedDefaultMethod {
 /*
 * Compile time error.
 * synchronized and default cannot be used together.
 */
 public synchronized default void print() {
  System.out.println("Hello world");
 }
}

Rule #7:
'static' and 'default' modifiers cannot be used together since the default methods are much similar to instance methods and there are static methods for interfaces, introduced in Java 8. For more information about static methods in interface follow this link.
public interface StaticDefaultMethod {
 /*
 * Compile time error.
 * static and default cannot be used together.
 */
 public static default void print() {
  System.out.println("Hello world");
 }
}

Rule #8:
Default methods are implicitly public, so using any other access modifiers caused to compile time error. The access modifier must be explicitly defined as public or simply leave it without defining any access modifier.
public interface NonPublicDefaultMethod {
    /*
    * Compile time error.
    * Default method must be public.
    */
    protected default void print() {
        System.out.println("Hello world");
    }

    /**
     * No error.
     * Default methods are implicitly public.
     */
    default void doStuff() {
        System.out.println("Default method");
    }

}

Diamond Problem

Everything seems to be fine BUT… can you smell the Diamond problem? If you are not familiar to Diamond problem then follow this link to learn about it in Wikipedia.
Java does not support multiple class to class inheritance to avoid the diamond problem but after the default methods now we need to worry about the diamond problem.

Consider the following code and try to predict the expected output without compiling it.
public class DiamondProblem {
 public static void main(String[] args) {
  A obj = new D();
  obj.print();
 }
}

interface A {
 default void print() {
  System.out.println("A");
 }
}

interface B extends A {
 default void print() {
  System.out.println("B");
 }
}

interface C extends A {
 default void print() {
  System.out.println("C");
 }
}

class D implements B, C {

}

Since the class D implements both interfaces B and C there are two methods print() inherited from its parents. In Java 8 it will caused to compile time error due to clash of default methods, but there are ways to solve this problem.

If a super interface provides a default method, and another interface supplies a method with the same name and parameter types (default or abstract), then you must resolve the conflict by overriding that method.
public class DiamondSolutionOne {
 public static void main(String[] args) {
  A obj = new D();
  obj.print();
 }
}

interface A {
 default void print() {
  System.out.println("A");
 }
}

interface B extends A {
 default void print() {
  System.out.println("B");
 }
}

interface C extends A {
 default void print() {
  System.out.println("C");
 }
}

/*
* The overridden method will get selected
*/
class D implements B, C {
 @Override
 public void print() {
  System.out.println("D");
 }
}

However if a super class provides a concrete method, default methods with the same name and parameter types are simply ignored.
public class DiamondSolutionTwo {
 public static void main(String[] args) {
  A obj = new D();
  obj.print();
 }
}

interface A {
 default void print() {
  System.out.println("A");
 }
}

interface B extends A {
 default void print() {
  System.out.println("B");
 }
}

interface C extends A {
 default void print() {
  System.out.println("C");
 }
}

class P {
 public void print() {
  System.out.println("P");
 }
}

/*
* Super class provides the implementation of print() method
*/
class D extends P implements B, C {

}

Find the source codes at Git Hub.
Previous
Next Post »

2 comments

Write comments
Gehan
AUTHOR
January 25, 2015 at 6:58 AM delete

A nice site with good explanations especially on Java 8 features.

Can you please elaborate on Rule #7.

Reply
avatar
January 25, 2015 at 8:57 PM delete

Hi Gehan,
I planned to write a separate post on static methods in interfaces. Since Java 8, you can create static methods with body inside interfaces. Default methods are equal to the instance methods. So we cannot define a method both static and instance method at the same time.

If you are wondering about the static methods follow this link:
http://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html#naturalOrder--

In java.util.Comparator interface naturalOrder() is a static method. You can call it as
Comparator comp = Comparator.naturalOrder();

Reply
avatar

Contact Form

Name

Email *

Message *