Java Default Methods
As you probably remember, interface methods are abstract by default. It means that they can't have a method body. Instead, they just declare a signature. One kind of method can have a body nevertheless. Such methods are called default
and are available since Java 8.
Methods with a body
Default methods are the opposite of abstract ones. They have an implementation:
interface Feature {
default void action() {
System.out.println("Default action");
}
}
To denote that a method is default, the default
keyword is reserved. Remember that an interface method is treated as abstract
by default. So you need to indicate this explicitly by putting the default
keyword before methods with a body, otherwise, a compilation error happens.
Although default methods are implemented, you cannot invoke them directly from an interface like Feature.action()
. You still need to have an object of a class that implements the interface:
class FeatureImpl implements Feature {
}
...
Feature feature = new FeatureImpl();
feature.action(); // Default action
If you want to customize a default method in a class, just override it like a regular method:
class FeatureImpl implements Feature {
public void action() {
System.out.println("FeatureImpl-specific action");
}
}
...
Feature feature = new FeatureImpl();
feature.action(); // FeatureImpl-specific action
Sometimes default methods are huge. To make it possible to decompose such methods, Java allows declaring private methods inside an interface:
interface Feature {
default void action() {
String answer = subAction();
System.out.println(answer);
}
private String subAction() {
return "Default action";
}
}
Why are they needed?
The main idea of an interface is declaring functionality. Default methods extend that idea. They don't just declare functionality but also implement it. The main reason is supporting backward compatibility. Let's consider an example.
Suppose you program a game that has several types of characters. These characters are able to move within a map. That is represented by the Movable
interface:
interface Movable {
void stepAhead();
void turnLeft();
void turnRight();
}
So, we have the interface and many classes that implement it. For example, a Batman
character:
class Batman implements Movable {
public void stepAhead() {...}
public void turnLeft() {...}
public void turnRight() {...}
}
Then you decide that characters should be able to turn around. This means you need to add the turnAround
method to Movable
. You may implement the method for all classes implementing the interface. Another way is to declare a default
method in the interface. Then you don't have to implement it in all classes.
Another example where the situation is getting even worse is when we are talking about interfaces that are part of the Java standard library. Suppose Java maintainers decided to enhance a commonly used interface with a new method in the next release. It means if you are going to upgrade the Java version and there are classes implementing the interface in your code, you have to implement the new method. Otherwise, your code won't compile.
Sometimes default methods help to avoid code duplication. Indeed in our case, turnAround
methods may look the same for all classes.
interface Movable {
void stepAhead();
void turnLeft();
void turnRight();
default void turnAround() {
turnLeft();
turnLeft();
}
}
If you want to customize a default implementation for Batman
, just override it:
class Batman implements Movable {
public void stepAhead() {...}
public void turnLeft() {...}
public void turnRight() {...}
public void turnAround() {
turnRight();
turnRight();
}
}
The diamond problem
Suppose we have another interface named Jumpable
that represents the ability to jump. The interface contains abstract methods for jumping in place and jumping while turning left and right. It also has a default
method for a turnaround jump with the same signature as Movable
.
interface Jumpable {
void jump();
void turnLeftJump();
void turnRightJump();
default void turnAround() {
turnLeftJump();
turnLeftJump();
}
}
Spiderman
has both abilities of Movable
and Jumpable
, so its class implements both interfaces. Note both interfaces have the default
method turnAround
with the same signature, but different implementations. Which one should be chosen for the class? To avoid this ambiguity, the compiler forces programmers to provide the implementation explicitly, otherwise it raises a compilation exception.
class Spiderman implements Movable, Jumpable {
// define an implementation for abstract methods
public void stepAhead() {...}
public void turnLeft() {...}
public void turnRight() {...}
public void jump() {…}
public void turnLeftJump() {...}
public void turnRightJump() {...}
// define an implementation for conflicting default method
public void turnAround() {
// define turnaround for Spiderman
}
}
You can also choose one of the default implementations instead of writing your own.
class Spiderman implements Movable, Jumpable {
...
public void turnAround() {
Movable.super.turnAround();
}
}
The problem in which a class implements different interfaces that have a default method with the same signature is known as the diamond problem.
Conclusion
Some interface methods have a body. Such methods are called default and have the default
keyword before their signature. The main idea of supporting default methods is to provide backward compatibility. This allows developers to add new methods to the existing interface without changing all classes that implement the interface. Remember that if a class implements several interfaces and some of them have a default method with the same method signature, you have to define the implementation for the method in the class. This happens because the compiler cannot decide which implementation should be used.