Default Methods in Java Interfaces

This article is a guide to the default methods in Java interfaces that explains their features, their benefits, and the problems they can cause with the help of examples.

Overview

By the end of this tutorial, you will have an in-depth understanding of the default methods in Java Interfaces. The tutorial begins by introducing the default methods and their features and then explains why Java introduced this feature. It will also cover how the default method can introduce method ambiguity problems and what are the ways to fix them.

What is a Default Method in Java Interface?

Java interfaces had only abstract methods until Java 8 introduced the default method capabilities into interfaces. Now, the interfaces can have a default implementation for a particular behaviour. These default methods must use the default modifier in their declaration.

A subclass can inherit default methods of its super interfaces or optionally override them to provide a specific implementation.

public interface FlyingToyWithDefault {
  void fly();

  default void rest() {
    System.out.println("If not told explicitly, " +
        "I rest like this");  
  }
}Code language: Java (java)

In this FlyingToyWithDefault interface, we have a mix of a default and an abstract method. The fly() method is an abstract method that every concrete subclass must override. However, the rest() method, a default method, completely implements its behaviour.

Inheriting a Default Method

Like any non-abstract method of the superclass, a class implementing an interface can inherit all the default methods from its interface.

public class FlyingCow implements FlyingToyWithDefault {
    @Override
    public void fly() {
        System.out.println("I am cow; I can fly");
    }
}Code language: Java (java)

The FlyingCow overrides the fly() method as it must and inherits the rest() method from the interface.

FlyingToyWithDefault cow = new FlyingCow();
cow.fly();
cow.rest();Code language: JavaScript (javascript)

That outputs to:

I am cow; I can fly
If not told explicitly, I rest like this

Overriding a Default Method

Like the superclass’s concrete methods, a class can override the default methods from their super interfaces.

public class Drone implements FlyingToyWithDefault {

  @Override
  public void fly() {
    System.out.println("I am drone; I can fly");
  }

  @Override
  public void rest() {
    System.out.println("I am drone; I need rest too");
  }
}Code language: Java (java)

The class in this example overrides both methods from its super interface. When a client invokes both of these methods, we can see the more specific method is run.

I am drone; I can fly
I am drone; I need rest too

Why Default Methods?

We see the default methods in Java Interfaces as a feature, which it is. However, there is a reason, which we will understand here, compelled Java to introduce this feature.

Traditionally, some of the core components of Java API heavily use interfaces. Java Collections framework also uses plenty of interfaces. For example, List, Queue, Set, etc., are all interfaces in Java.

With the introduction of Java Streams, Java wanted to modify collections API to introduce new behaviours in their collections. However, adding more behaviours to an interface would have failed its existing implementations. To maintain backward compatibility, Java introduced the concept of default interfaces. Because of the default methods, they can add new methods – with default behaviours – to the interfaces, and their preexisting implementations did not break.

An Interface Overrides a Default Method

Like any class, an interface can override the super interface’s default method. The rules of inheritance are the same for classes and interfaces with default methods.

Let’s understand it with an example.

public interface Device {
  default void start() {
    System.out.println("Device is starting");
  }
}Code language: Java (java)
public interface SmartDevice extends Device{
  @Override
  default void start(){
    System.out.println("Smart Device is starting");
  }
}
Code language: Java (java)

As the snippet shows, the SmartDevice extends the Device interface, and both have a default method with the same name and signature.

What happens when a subclass of SmartDevice wants to inherit the default method? Will it invoke the immediate interface’s method or the super interface’s?

Let’s find the answer practically.

public class SmartGadget implements SmartDevice {
}Code language: Java (java)

Now, let’s create a client which invokes the start() method using Device, SmartDevice, and SmartGadget references, respectively.

Device device = new SmartGadget();
device.start();

SmartDevice smartDevice = new SmartGadget();
smartDevice.start();

SmartGadget smartGadget = new SmartGadget();
smartGadget.start();Code language: Java (java)

As shown in the following output, in all three cases, the SmartDevice method runs.

Smart Device is starting
Smart Device is starting
Smart Device is starting

Thus we proved that when a class overrides a default method from its interface, and the interface, in turn, overrides the method from its super interface, invoking the method on the class invokes the immediate interface’s method.

Interfaces and Multiple-Inheritance Rules

Java allows for a class to implement multiple interfaces. That never caused the multiple-inheritance conflict before the introduction of the default methods. However, with the default methods, there is a possibility that more than one super interface can have a default method of the same signature. That causes method ambiguity; thus, we must understand an interface’s behaviour during multiple-inheritance ambiguity.

We will create a Phone interface similar to the previously created SmartDevice interface and let our class implement both. Note that both interfaces have default methods of the same signature and name.

public interface Phone {
  default void start(){
    System.out.println("Phone is starting");
  }
}Code language: Java (java)

The following code won’t compile because the class intends to inherit a method from one of its interfaces, and both interfaces have the method. This situation is an example of method ambiguity caused by multiple-inheritance.

public class SmartGadget
    implements SmartDevice, Phone {
}Code language: Java (java)

In the case of method ambiguity, Java enforces the sub-class to override the default method as if it is an abstract method.

Solve Method Ambiguity caused by Multiple-Inheritance

Let’s see How to solve the method-ambiguity problem that multiple-inheritance with non-abstract method causes.

Ambiguity by Multiple Interfaces with Same Methods

When multiple interfaces have a default method of the same signature, the implementing class must override the ambiguous method. The subclass can solve the method-ambiguity problem by explicitly invoking the method on a particular super interface.

The subclass can use a special syntax, highlighted in the following example, to invoke a super interface’s default method.

public class SmartGadget
    implements SmartDevice, Phone {
  @Override
  public void start() {
    System.out.println("Starting as a phone...");
    Phone.super.start();
  }
}
Code language: Java (java)

When a client invokes the start() method, the SmartGadget‘s method runs, invoking the method from the Phone interface.

Ambiguity by An Abstract Class and Interfaces with Same Methods

Similarly, consider a class that extends another class with a non-abstract method and implements an interface with a default method of the same signature.

Although that is a case of multiple-inheritance, it doesn’t produce a method-ambiguity situation. That is because when a superclass and a super interface have non-abstract methods of the same signature, the subclass always inherits the method from the superclass (and not from the super interface).

To demonstrate that, let’s create an abstract Gadget class having the start() method.

public abstract class Gadget {
  public void start() {
    System.out.println("Gadget is starting");
  }
}Code language: Java (java)

Let’s change the SmartGadget class and make it extend the abstract class. As previously stated, the following code doesn’t cause method ambiguity and compiles fine.

public class SmartGadget
    extends Gadget
    implements SmartDevice, Phone {
}Code language: Java (java)

Let a client invoke the method using all four possible references.

Device device = new SmartGadget();
device.start();

SmartDevice smartDevice = new SmartGadget();
smartDevice.start();

SmartGadget smartGadget = new SmartGadget();
smartGadget.start();

Gadget gadget = new SmartGadget();
gadget.start();Code language: Java (java)
Gadget is starting
Gadget is starting
Gadget is starting
Gadget is starting

From the output, it is clear that no matter which reference type the client uses, it will always invoke the superclass’s method.

Summary

This tutorial covered an in-depth overview of the default methods in Java Interfaces. We understood the features and rules of creating default methods in an Interface. Also, we uncovered the reasons behind the introduction of the default methods.

Finally, we covered How the interfaces with the default method can produce method ambiguities and how to solve the method ambiguities that multiple-inheritance causes.

Refer to our Github Repository for the complete source code of the examples used in this tutorial.