Java 8 Static and Default Methods in Interface

1. Overview

In this post, we’ll discuss in depth how to use static and default methods in interfaces and go through some use cases where they can be useful.
As we know, In the Java programming language, an interface is a reference type, similar to a class, that can contain only constants, method signatures, default methods, static methods, nested types and private methods(from Java 9).
Method bodies exist only for default methods and static methods (private methods from Java 9). Interfaces cannot be instantiated—they can only be implemented by classes or extended by other interfaces.
Java 8 brought to the table a few brand new features, including lambda expressions, functional interfaces, method references, streams, Optional, and static and default methods in interfaces.

2. Some Real-world Examples

  1. The stream() method defined in the Collection interface is a good example. You can find below default method in Collection interface.
 default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
 }
AbstractCollection and its subclasses as ArrayList don't need to define it. It is directly inherited from the interface.
  1. The chars() method in CharSequence interface..You can find below default method in CharSequence interface.
public default IntStream chars() {
        class CharIterator implements PrimitiveIterator.OfInt {
            int cur = 0;

            public boolean hasNext() {
                return cur < length();
            }

            public int nextInt() {
                if (hasNext()) {
                    return charAt(cur++);
                } else {
                    throw new NoSuchElementException();
                }
            }

            @Override
            public void forEachRemaining(IntConsumer block) {
                for (; cur < length(); cur++) {
                    block.accept(charAt(cur));
                }
            }
        }

        return StreamSupport.intStream(() ->
                Spliterators.spliterator(
                        new CharIterator(),
                        length(),
                        Spliterator.ORDERED),
                Spliterator.SUBSIZED | Spliterator.SIZED | Spliterator.ORDERED,
                false);
    }
There are lot's of default and static methods are added to JDK 7 libraries and ensure binary compatibility with code written for older versions of those interfaces.

3. Default Method in Interface

3.1 Why Default Methods in Interfaces Are Needed

  • 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.
  • In a typical design based on abstractions, where an interface has one or multiple implementations, if one or more methods are added to the interface, all the implementations will be forced to implement them too. Otherwise, the design will just break down so default interface methods are an efficient way to deal with this issue. They allow us to add new methods to an interface that are automatically available in the implementations. Thus, there’s no need to modify the implementing classes. In this way, backward compatibility is neatly preserved without having to refactor the implementers.
  • The default method is used to define a method with a default implementation. You can override the default method also to provide the more specific implementation for the method.

3.2 Default Methods in Interface Examples

To better understand the functionality of default interface methods, let’s create a simple example.
Step 1: Creare Vehicle interface and just one implementation. Let's first discuss just one implementation of default methods of Vehicle interface.
public interface Vehicle {
 String getBrand();

 String speedUp();

 String slowDown();

 default String turnAlarmOn() {
  return "Turning the vehice alarm on.";
 }

 default String turnAlarmOff() {
  return "Turning the vehicle alarm off.";
 }

 static int getHorsePower(int rpm, int torque) {
  return (rpm * torque) / 5252;
 }
}
Step 2: let’s write the implementing class for Vehicle interface.
public class Car implements Vehicle {

    private final String brand;

    public Car(String brand) {
        this.brand = brand;
    }

    @Override
    public String getBrand() {
        return brand;
    }

    @Override
    public String speedUp() {
        return "The car is speeding up.";
    }

    @Override
    public String slowDown() {
        return "The car is slowing down.";
    }
}
Step 3: Let's test the above implementation with main() method.
Please notice how the default methods turnAlarmOn() and turnAlarmOff() from our Vehicle interface are automatically available in the Car class.
public class TestJava8Interface {
 public static void main(String[] args) {

  Vehicle car = new Car("BMW");
  System.out.println(car.getBrand());
  System.out.println(car.speedUp());
  System.out.println(car.slowDown());
  System.out.println(car.turnAlarmOn());
  System.out.println(car.turnAlarmOff());
  System.out.println(Vehicle.getHorsePower(2500, 480));
  
 }
}
Output :
BMW
The car is speeding up.
The car is slowing down.
Turning the vehice alarm on.
Turning the vehicle alarm off.
228
Now, we write two implementations for Vehicle interface and test the default methods behavior.
Let's create Motorbike class which implements Vehicle interface.
public class Motorbike implements Vehicle {

    private final String brand;

    public Motorbike(String brand) {
        this.brand = brand;
    }

    @Override
    public String getBrand() {
        return brand;
    }

    @Override
    public String speedUp() {
        return "The motorbike is speeding up.";
    }

    @Override
    public String slowDown() {
        return "The motorbike is slowing down.";
    }
}
Let's test the two implementations for Vehicle interface with main() method.
The most typical use of default methods in interfaces is to incrementally provide additional functionality to a given type without breaking down the implementing classes.
public class TestJava8Interface {
 public static void main(String[] args) {

  Vehicle car = new Car("BMW");
  System.out.println(car.getBrand());
  System.out.println(car.speedUp());
  System.out.println(car.slowDown());
  System.out.println(car.turnAlarmOn());
  System.out.println(car.turnAlarmOff());
  System.out.println(Vehicle.getHorsePower(2500, 480));
  
  Vehicle bike = new Motorbike("ACTIVA 4G");
  System.out.println(bike.getBrand());
  System.out.println(bike.speedUp());
  System.out.println(bike.slowDown());
  System.out.println(bike.turnAlarmOn());
  System.out.println(bike.turnAlarmOff());
  System.out.println(Vehicle.getHorsePower(2500, 480));
  
 }
}
output :
BMW
The car is speeding up.
The car is slowing down.
Turning the vehice alarm on.
Turning the vehicle alarm off.
228
ACTIVA 4G
The motorbike is speeding up.
The motorbike is slowing down.
Turning the vehice alarm on.
Turning the vehicle alarm off.
228

3.3 Multiple Interface Inheritance Rules

We know that Java allows classes to implement multiple interfaces, it’s important to know what happens when a class implements several interfaces that define the same default methods.
To better understand this scenario, let’s define a new Alarm interface.
public interface Alarm {
 default String turnAlarmOn() {
  return "Turning the alarm on.";
 }

 default String turnAlarmOff() {
  return "Turning the alarm off.";
 }
}
Now, let Car class implement two interfaces Vehicle and Alaram. The source code for the same is:
public class MultiAlarmCar implements Vehicle, Alarm {

    private final String brand;

    public MultiAlarmCar(String brand) {
        this.brand = brand;
    }

    @Override
    public String getBrand() {
        return brand;
    }

    @Override
    public String speedUp() {
        return "The motorbike is speeding up.";
    }

    @Override
    public String slowDown() {
        return "The mootorbike is slowing down.";
    }

}
In this case, the compiler reports an error like Duplicate default methods named turnAlarmOff are inherited from the types Alarm and Vehicle.
To solve this ambiguity, we must explicitly provide an implementation for the methods:
@Override
public String turnAlarmOn() {
    // custom implementation
}
     
@Override
public String turnAlarmOff() {
    // custom implementation
}
We can also have our class use the default methods of one of the interfaces. Let’s see an example that uses the default methods from the Vehicle interface:
@Override
public String turnAlarmOn() {
    return Vehicle.super.turnAlarmOn();
}
 
@Override
public String turnAlarmOff() {
    return Vehicle.super.turnAlarmOff();
}
Similarly, we can have the class use the default methods defined within the AlarmInterface:
@Override
public String turnAlarmOn() {
    return Alarm.super.turnAlarmOn();
}
 
@Override
public String turnAlarmOff() {
    return Alarm.super.turnAlarmOff();
}
Furthermore, it’s even possible to make the Car class use both sets of default methods:
@Override
public String turnAlarmOn() {
    return Vehicle.super.turnAlarmOn() + " " + Alarm.super.turnAlarmOn();
}
     
@Override
public String turnAlarmOff() {
    return Vehicle.super.turnAlarmOff() + " " + Alarm.super.turnAlarmOff();
}

4. Static Method in Interface

Generally, static methods are used to define utility methods.
The idea behind static interface methods is to provide a simple mechanism that allows us to increase the degree of cohesion of a design by putting together related methods in one single place without having to create an object.
Furthermore, static methods in interfaces make possible to group related utility methods, without having to create artificial utility classes that are simply placeholders for static methods.

4.1 Example of a static method in Interface

public interface Vehicle {
 String getBrand();

 String speedUp();

 String slowDown();

 default String turnAlarmOn() {
  return "Turning the vehice alarm on.";
 }

 default String turnAlarmOff() {
  return "Turning the vehicle alarm off.";
 }

 static int getHorsePower(int rpm, int torque) {
  return (rpm * torque) / 5252;
 }
}
There is getHorsePower(int, int) static method in Vehicle interface.
Let's see how the client uses this method :
public class TestJava8Interface {
 public static void main(String[] args) {

  Vehicle car = new Car("BMW");
  System.out.println(car.getBrand());
  System.out.println(car.speedUp());
  System.out.println(car.slowDown());
  System.out.println(car.turnAlarmOn());
  System.out.println(car.turnAlarmOff());
  System.out.println(Vehicle.getHorsePower(2500, 480));
 }
}
The source code of this post is available on GitHub Repository.

4. Related Java 8 Top Posts

Free Spring Boot Tutorial | Full In-depth Course | Learn Spring Boot in 10 Hours


Watch this course on YouTube at Spring Boot Tutorial | Fee 10 Hours Full Course

Comments