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.

Java

1

defaultvoidforEach(Consumer<?superT>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.

Java

1

2

3

4

publicinterfaceNamedPerson{

StringfirstName();

StringlastName();

}

You also have two classes (Student and Contact) that implement this interface.

Java

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

classStudentimplementsNamedPerson{

privateStringfName,lName;

publicStudent(Stringfirst,Stringlast){

this.fName=first;

this.lName=last;

}

publicStringfirstName(){

returnfName;

}

publicStringlastName(){

returnlName;

}

}

classContactimplementsNamedPerson{

privateStringfName,lName;

publicContact(Stringfirst,Stringlast){

this.fName=first;

this.lName=last;

}

publicStringfirstName(){

returnfName;

}

publicStringlastName(){

returnlName;

}

}

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.

Java

1

2

3

4

5

publicinterfaceNamedPerson{

StringfirstName();

StringlastName();

StringfullName();

}

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.

Java

1

2

3

4

5

6

7

8

publicinterfaceNamedPerson{

StringfirstName();

StringlastName();

defaultStringfullName(){

returnfirstName()+" "+lastName();

}

}

Testing Default Methods

You can test this with a simple program.

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

importjava.util.Arrays;

importjava.util.List;

classMain{

publicstaticvoidmain(String[]args){

List<NamedPerson>persons=Arrays.asList(

newStudent("Albert","Einstein"),

newContact("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.

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

classContactimplementsNamedPerson{

privateStringfName,lName;

publicContact(Stringfirst,Stringlast){

this.fName=first;

this.lName=last;

}

@Override

publicStringfirstName(){

returnfName;

}

@Override

publicStringlastName(){

returnlName;

}

@Override

publicStringfullName(){

returnlastName()+", "+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.

Java

1

2

3

4

5

6

7

8

publicinterfaceAddressBookEntry{

StringgivenName();

StringfamilyName();

defaultStringfullName(){

returngivenName()+" "+familyName();

}

}

You want the Contact class to implement both the NamedPerson and AddressBookEntry interfaces.

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

classContact implementsNamedPerson,AddressBookEntry{

privateStringfName,lName;

publicContact(Stringfirst,Stringlast){

this.fName=first;

this.lName=last;

}

@Override

publicStringfirstName(){

returnfName;

}

@Override

publicStringlastName(){

returnlName;

}

@Override

publicStringgivenName(){returnfName;}

@Override

publicStringfamilyName(){returnlName;}

}

However, your code would not compile. The compiler would throw the following error since it can’t figure out which default implementation to use.