Default Method introduced in Java 8, allows developers to add new methods to an interface without breaking the existing implementations of this interface. It provides flexibility to allow the interface to define an implementation which will be used as default when a class which implements that interface fails to provide an implementation of
that method.
Basic use for object null check
For null check in method Object nullableObject = methodReturnObject(); if (Objects.isNull(nullableObject)) { return; } For not null check in method Object nullableObject = methodReturnObject(); if (Objects.nonNull(nullableObject)) { return; }
Objects.nonNull() method reference use in stream api
In the old fashion way for collection null check
List<object> someObjects = metodGetList(); for (Object obj : someObjects) { if (obj == null) { continue; } doSomething(obj); }
With the Objects.nonNull method and Java8 Stream API, we can do the above in this way:
List<Object> someObjecs = methodGetList(); someObjects.stream().filter(Objects::nonNull).forEach(this::doSomething);
Basic usage of default methods
/** * Interface with default method * / public interface Printable { default void printString() { System.out.println( "default implementation" ); } } /** * Class which falls back to default implementation of {@link #printString()} */ public class WithDefault implements Printable { } /** * Custom implementation of {@link #printString()} * / public class OverrideDefault implements Printable { @Override public void printString() { System.out.println( "overridden implementation" ); } }
The following statements
new WithDefault().printString();
new OverrideDefault().printString();
Will produce this output:
default implementation
overridden implementation
Accessing overridden default methods from implementing class
In classes, super.foo() will look in superclasses only. If you want to call a default implementation from a superinterface, you need to qualify super with the interface name: Fooable.super.foo().
public interface Fooable { default int foo() {return 3;} } public class A extends Object implements Fooable { @Override public int foo() { //return super.foo() + 1; //error: no method foo() in java.lang.Object return Fooable.super.foo() + 1; //okay, returns 4 } }
Why use Default Methods?
The simple answer is that it allows you to evolve an existing interface without breaking existing implementations.
Related Article: Classes and Objects in Java
For example, you have Swim interface that you published 20 years ago.
public interface Swim { void backStroke(); }
We did a great job, our interface is very popular, there are many implementation on that around the world and you don’t have control over their source code.
public class FooSwimmer implements Swim { public void backStroke() { System.out.println("Do backstroke"); } }
After 20 years, you’ve decided to add new functionality to the interface, but it looks like our interface is frozen because it will break existing implementations.
Luckily Java 8 introduces brand new feature called Default method.
We can now add new method to the Swim interface.
Now all existing implementations of our interface can still work. But most importantly they can implement the newly added method in their own time.
One of the biggest reasons for this change, and one of its biggest uses, is in the Java Collections framework. Oracle could not add a foreach method to the existing Iterable interface without breaking all existing code which
implemented Iterable. By adding default methods, existing Iterable implementation will inherit the default implementation.
Accessing other interface methods within default method
You can as well access other interface methods from within your default method.
public interface Summable { int getA(); int getB(); default int calculateSum() { return getA() + getB(); } } public class Sum implements Summable { @Override public int getA() { return 1; } @Override public int getB() { return 2; } } The following statement will print 3:
System.out.println(new Sum().calculateSum());
Default methods could be used along with interface static methods as well:
public interface Summable { static int getA() { return 1; } static int getB() { return 2; } default int calculateSum() { return getA() + getB(); } } public class Sum implements Summable {}
The following statement will also print 3:
System.out.println(new Sum().calculateSum());
Default method multiple inheritance collision
Consider next example:
public interface A { default void foo() { System.out.println("A.foo"); } } public interface B { default void foo() { System.out.println("B.foo"); } }
Here are two interfaces declaring default method foo with the same signature.
If you will try to extend these both interfaces in the new interface you have to make choice of two, because Java forces you to resolve this collision explicitly.
First, you can declare method foo with the same signature as abstract, which will override A and B behaviour.
public interface ABExtendsAbstract extends A, B { @Override void foo(); }
And when you will implement ABExtendsAbstract in the class you will have to provide foo implementation:
public class ABExtendsAbstractImpl implements ABExtendsAbstract { @Override public void foo() { System.out.println("ABImpl.foo"); } }
Or second, you can provide a completely new default implementation. You also may reuse code of A and B foo methods by Accessing overridden default methods from implementing class.
public interface ABExtends extends A, B { @Override default void foo() { System.out.println("ABExtends.foo"); } }
And when you will implement ABExtends in the class you will not have to provide foo implementation:
public class ABExtendsImpl implements ABExtends {}
Class, Abstract class and Interface method precedence
Implementations in classes, including abstract declarations, take precedence over all interface defaults.
- Abstract class method takes precedence over Interface Default Method.
public interface Swim { default void backStroke() { System.out.println("Swim.backStroke"); } } public abstract class AbstractSwimmer implements Swim { public void backStroke() { System.out.println("AbstractSwimmer.backStroke"); } } public class FooSwimmer extends AbstractSwimmer { }
The following statement
new FooSwimmer().backStroke();
Will produce
AbstractSwimmer.backStroke
Class method takes precedence over Interface Default Method
public interface Swim { default void backStroke() { System.out.println("Swim.backStroke"); } } public abstract class AbstractSwimmer implements Swim { } public class FooSwimmer extends AbstractSwimmer { public void backStroke() { System.out.println("FooSwimmer.backStroke"); } }
The following statement
new FooSwimmer().backStroke();
Will produce
FooSwimmer.backStroke