Default and Static Methods in Java 8
Default methods and Static methods were introduced in Java 8. Before Java 8, all methods in an interface were public and abstract by default. Java 8 allows developers to add new methods in interfaces without making any changes in the classes that implement these interfaces.
The need for default and static methods
Consider a scenario in which there is an interface Sortable<T> that has two methods: void sort(); and T peek();. There could be several classes that implement the interface Sortable<T>. For example: SortableNumberCollection and SortableStringCollection.
Before Java 8, If we have to now add a new method String sortAndPeek() to the Sortable<T> interface, then it would result in changing all the classes that implement the interface. It may also result in duplicate code if the logic of the method implemented by the classes is the same. This could become even more difficult if the interface is part of a library used in many software solutions or if there are hundreds of classes that may be implementing the interface.
In Java 8, this problem can be resolved by implementing a ‘default method’. A default method can be added to any existing interfaces without the need to modify the classes that implement it. The default methods provide a capability for the interface to be backward compatible. It also reduces duplicate code when the implementation of the method is supposed to be the same.
The ‘static methods’ are similar to default methods, except that they cannot be overridden by the implementing classes.
Why don’t we use Abstract classes instead of Interfaces?
The basic concept of interfaces and abstract classes is different according to OOPs principles. The abstract classes are typically used where there is an ‘is a’ relation. For example, the classes: Car, Van, Ship, and Airplane can be a subclass of an abstract class Vehicle. These classes have an ‘is a’ relationship with the abstract class. E.g: Car ‘is a’ Vehicle.
Interfaces, on the other hand, are used to specify contracts or behavior of unrelated subclasses. It has a ‘is’ relationship. For example, both Car and Van can implement an interface Drivable and implement a method boolean drive(). Unrelated classes like RemoteControlledToyCar, AngerEmotion could also implement the Drivable interface.
The Ship and Airplane classes could implement other interfaces like Sailable and Flyable respectively with very different methods. These classes have an ‘is’ relationship with the interface. E.g: Car ‘is’ Drivable.
The abstract classes are partial abstractions that could have constructors that can be called by the subclasses. The interfaces provide full abstractions and do not contain constructors.
Read more about in “When to Use Abstract Class and Interface”.
Example of default method in Java 8
Let us consider the Sortable<T> interface and the classes, SortableNumberCollection and SortableStringCollection, that implement the interface. The interface has two methods: void sort(); and T peek();.
public interface Sortable<T> { void sort(); T peek(); }
The void sort(); method in the interface can be called to sort items. The T peek(); method in the interface could be used to get a particular item.
We would need a comparator class ObjectComparator to perform comparisons of two items so that it can be used for sorting.
public class ObjectComparator implements Comparator<Comparable> { @Override public int compare(Comparable o1, Comparable o2) { return o1.compareTo(o2); } }
The SortableStringCollection is a custom collection class that holds a list of strings that could be sorted and peeked using the interface methods. The implementation of the class would be:
public class SortableStringCollection implements Sortable<String> { private List<String> items = new ArrayList<>(); public void add(String item) { items.add(item); } @Override public void sort() { items.sort(new ObjectComparator()); } @Override public String peek() { return items.get(0); } }
Similarly, the SortableNumberCollection is a custom collection class that holds a list of numbers that could be sorted and peeked using the interface methods. The implementation of the class would be:
public class SortableNumberCollection implements Sortable<Integer> { private List<Integer> items = new ArrayList<>(); public void add(Integer item) { items.add(item); } @Override public void sort() { items.sort(new ObjectComparator()); } @Override public Integer peek() { return items.get(0); } }
Adding a new method “before Java 8”
Now let us take the scenario when we need to add a new method T sortAndPeek(); to the Sortable<T> interface. This method would internally call the void sort(); method followed by T peek(); method. The implementation of the interface and the classes would be:
public interface Sortable<T> { void sort(); T peek(); T sortAndPeek(); // New method added. }
All the classes that implement this interface will now have to implement the method. In our example, the SortableStringCollection and the SortableNumberCollection classes should implement this method. The implementation of the class would be:
public class SortableStringCollection implements Sortable<String> { private List<String> items = new ArrayList<>(); public void add(String item) { items.add(item); } @Override public void sort() { items.sort(new ObjectComparator()); } @Override public String peek() { return items.get(0); } @Override public String sortAndPeek() { // Implemeting the new method. sort(); return peek(); } }
public class SortableNumberCollection implements Sortable<Integer> { private List<Integer> items = new ArrayList<>(); public void add(Integer item) { items.add(item); } @Override public void sort() { items.sort(new ObjectComparator()); } @Override public Integer peek() { return items.get(0); } @Override public Integer sortAndPeek() { // Implemeting the new method. sort(); return peek(); } }
If there were 100 more such classes, it would have been a very difficult task to manage all of them.
Adding a new method in “Java 8 and above”
The ‘default methods’ in Java 8 makes it easier to add a new method T sortAndPeek(); to the Sortable<T> interface without any changes required for the classes that implement it. The implementation of the interface in java 8 would be:
public interface Sortable<T> { void sort(); T peek(); default T sortAndPeek(){ // New 'default method' added in the interface sort(); return peek(); } }
The original SortableStringCollection and the SortableNumberCollection classes wouldn’t need any changes. The implementation of the class would remain the same:
public class SortableStringCollection implements Sortable<String> { private List<String> items = new ArrayList<>(); public void add(String item) { items.add(item); } @Override public void sort() { items.sort(new ObjectComparator()); } @Override public String peek() { return items.get(0); } }
public class SortableNumberCollection implements Sortable<Integer> { private List<Integer> items = new ArrayList<>(); public void add(Integer item) { items.add(item); } @Override public void sort() { items.sort(new ObjectComparator()); } @Override public Integer peek() { return items.get(0); } }
It is also possible to override the default implementation of the method in any of the classes that implement this interface if need be.
Resolving default methods in multiple inheritances
If two or more interfaces have the same default method signature and a class implements both the interfaces, it would throw a compile-time error. For example:
public interface Interface1 { void methodOne(String str); default void newMethod(){ System.out.println("Interface1: Newly added method"); } } public interface Interface2 { void methodTwo(String str); default void newMethod(){ System.out.println("Interface2: Newly added method"); } } public class InterfaceImplementation implements Interface1, Interface2{ @Override public void methodOne(String str) { System.out.println("Overridden methodOne: " + str); } @Override public void methodTwo(String str) { System.out.println("Overridden methodTwo: " + str ); } }
The compilation of the class InterfaceImplementation would result in the following compile-time error message:
InterfaceImplementation inherits unrelated defaults for newMethod from types Interface1 and Interface2
To resolve this problem, we will have to override the method in the class InterfaceImplementation:
public class InterfaceImplementation implements Interface1, Interface2{ @Override public void methodOne(String str) { System.out.println("Overridden methodOne: " + str); } // newMethod implemented to resolve the conflict. @Override public void newMethod() { System.out.println("InterfaceImplementation: Newly added method"); } @Override public void methodTwo(String str) { System.out.println("Overridden methodTwo: " + str ); } }
Adding a static method in Java 8
‘Static methods’ can also be added in interfaces, just like ‘default methods’. For example, we can add a static method Direction getDefaultDirection() that would return a default direction like:
public interface Sortable<T> { Direction defaultDirection = Direction.DESC; enum Direction { ASC, DESC }; void sort(); T peek(); static Direction getDefaultDirection(){ // 'static method' added to the interface. return defaultDirection; } }
In the above example, the static Direction getDefaultDirection() method can be called using the class reference:
Sortable.getDefaultDirection()
See the complete source code in my github repository.