How to Use Java 8’s Default Methods
Before Java 8 your interfaces could contain method declarations, but no implementation code. One of the new features of Java 8 is the option to provide default implementations for methods right in your interface code itself.
Backward Compatibility
The main reason for adding this functionality was to allow the Java 8 team to add new methods to the Collections API (e.g. the forEach()
method). Without this, existing third-party libraries that already implement the Java collections API would not have worked with Java 8. These vendors would have been forced to upgrade their code for Java 8. Even worse, they would have to create and maintain separate versions if they wanted to continue supporting their non-Java 8 users.
Instead of breaking backward compatibility, the Java 8 team came up with a clever solution. Allow developers to include a default implementation for their methods right in the interface itself — very similar to abstract classes. For example, the Iterable
class now declares a default implementation for the forEach method.
1 |
default void forEach(Consumer<? super T> action) |
Default Methods in Action
Let’s understand how you can use default methods with an example. Assume that you have an interface called NamedPerson.
1 2 3 4 |
public interface NamedPerson { String firstName(); String lastName(); } |
You also have two classes (Student
and Contact
) that implement this interface.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
class Student implements NamedPerson { private String fName, lName; public Student(String first, String last) { this.fName = first; this.lName = last; } public String firstName() { return fName; } public String lastName() { return lName; } } class Contact implements NamedPerson { private String fName, lName; public Contact(String first, String last) { this.fName = first; this.lName = last; } public String firstName() { return fName; } public String lastName() { return lName; } } |
Now, suppose you wanted to add a method that returns the full name of the person long after the interface had been released and implemented by other developers in their own apps. You would start by adding the new method as usual.
1 2 3 4 5 |
public interface NamedPerson { String firstName(); String lastName(); String fullName(); } |
If you left the interface like this, both your Student
and Contact
classes would have to change. Plus any developer implementing this interface would also have to change their class.
To avoid this in Java 8, you just need to add a default implementation to your fullName()
method in the NamedPerson interface itself.
1 2 3 4 5 6 7 8 |
public interface NamedPerson { String firstName(); String lastName(); default String fullName() { return firstName() + " " + lastName(); } } |
Testing Default Methods
You can test this with a simple program.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import java.util.Arrays; import java.util.List; class Main { public static void main(String[] args) { List<NamedPerson> persons = Arrays.asList( new Student("Albert", "Einstein"), new Contact("Albert", "Pinto")); for(NamedPerson person : persons) { System.out.println(person.fullName()); } } } |
This gives the following output.
1 2 |
Albert Einstein Albert Pinto |
Of course, you can always override fullName()
at any time. For example, if you wanted to use the format “Last Name, First Name” in the Contact class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class Contact implements NamedPerson { private String fName, lName; public Contact(String first, String last) { this.fName = first; this.lName = last; } @Override public String firstName() { return fName; } @Override public String lastName() { return lName; } @Override public String fullName() { return lastName() + ", " + firstName(); } } |
Running the program now gives you the following output.
1 2 |
Albert Einstein Pinto, Albert |
As you can see, the Student
class is using the default implementation from NamedPerson
while the Contact
class is using its own custom implementation.
Multiple Inheritance Conflicts
It is possible for your classes to implement two interfaces that both supply default implementations of the same method. For example, suppose you have another interface named AddressBookEntry
.
1 2 3 4 5 6 7 8 |
public interface AddressBookEntry { String givenName(); String familyName(); default String fullName() { return givenName() + " " + familyName(); } } |
You want the Contact
class to implement both the NamedPerson
and AddressBookEntry
interfaces.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class Contact implements NamedPerson, AddressBookEntry { private String fName, lName; public Contact(String first, String last) { this.fName = first; this.lName = last; } @Override public String firstName() { return fName; } @Override public String lastName() { return lName; } @Override public String givenName() { return fName; } @Override public String familyName() { return lName; } } |
However, your code would not compile. The compiler would throw the following error since it can’t figure out which default implementation to use.
1 2 3 |
Contact.java:1: error: class Contact inherits unrelated defaults for fullName() from types NamedPerson and AddressBookEntry class Contact implements NamedPerson, AddressBookEntry |
Resolving this error is quite simple. You just need to provide a custom implementation of fullName()
in the Contact
class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
class Contact implements NamedPerson, AddressBookEntry { private String fName, lName; public Contact(String first, String last) { this.fName = first; this.lName = last; } @Override public String firstName() { return fName; } @Override public String lastName() { return lName; } @Override public String givenName() { return fName; } @Override public String familyName() { return lName; } @Override public String fullName() { return lName + ", " + fName; } } |
This resolves the error as the compiler can now use the class’ own implementation of fullName()
and doesn’t have to choose one of its defaults.
Wish they used better wording on that error message.
Why couldn’t this:
‘class Contact inherits unrelated defaults for fullName()…’
Be better worded like this:
‘class Contact inherits conflicting defaults for fullName()…’
Just one word but it makes a world of difference when trying to debug/troubleshoot.
My 0.02,
Sal.
Agreed. Good error messages are an unsung hero.
Very well explained. Looking forward for a series of Java 8 new features explained and maintained this way (with code snippets). Thanks
Glad you found it useful and yes, we do have more Java 8 posts in the works. Cheers.