Core Java

Java 8 default methods: what can and can not do?

What default method is

With the release of Java 8 you can modify interfaces adding new methods so that the interface remains compatible with the classes that implement the interface. This is very important in case you develop a library that is going to be used by several programmers from Kiev to New York. Until the dawn of Java 8 if you published an interface in a library you could not add a new method without risking that some application implementing in the interface will break with the new version of the interface.

With Java 8 this fear is gone? No.

Adding a default method to an interface may render some class unusable.

Let’s see first the fine points of the default method.

In Java 8 a method can be implemented in an interface. (Static methods can also be implemented in an interface as of Java8, but that is another story.) The method implemented in an interface is called default method and is denoted by the keyword default as a modifier. When a class implements an interface it may, but does not need to implement a method implemented already in the interface. The class inherits the default implementation. This is why you may not need touch a class when an interface it implements changes.

Multiple inheritance?

The things start to get complicated when a concrete class implements more than one (say two) interfaces and the interfaces implement the same default method. Which default method will the class inherit? The answer is none. In such a case the class has to implement the method itself (directly or by inheritance from a higher class).

This is also true when only one of the interfaces implement the default method and the other one only declares it as abstract. Java 8 tries to be disciplined and avoid “implicit” things. If the methods are declared in more than one interfaces then no default implementation is inherited, you get a compile time error.

However you can not get a compile time error if you have your class already compiled. This way Java 8 is not consistent. It has its reason, which I do not want to detail here or get into debate for various reasons (e.g.: the release is out, debate time is long over and was never on this platform).

  • Say you have two interfaces, and a class implementing the two interfaces.
  • One of the interfaces implement a default method m().
  • You compile all the interfaces and the class.
  • You change the interface not containing the method m() to declare it as an abstract method.
  • Compile the modified interface only.
  • Run the class.

multiple-inheritance
In this case the class runs. You can not compile it again with the modified interfaces, but if it was compiled with the older version: it still runs. Now

  • modify the interface having the abstract method m() and create a default implementation.
  • Compile the modified interface.
  • Run the class: failure.

When there are two interfaces providing default implementation for the same method the method can not be invoked in the implementing class unless implemented by the class (again: either directly or inherited from another class).
 
multiple-inheritance2
The class is compatible. It can be loaded with the new interface. It can even start execution so long as long there is no invocation to the method having default implementation in both interfaces.

Sample code

multiple-inheritance-directory

To demonstrate the above I created a test directory for the class C.java and three subdirectories for the interfaces in files I1.java and I2.java. The root directory of the test contains the source code for the class C in file C.java. The directory base contains the interface version that is good for execution and compilation. I1 contains the method m() with default implementation. The interface I2 does not contain any method for now.

The class contains a main method so we can execute it in our test. It tests if there is any command line argument so we can easily execute it with and without invoking the method m().

~/github/test$ cat C.java 
public class C implements I1, I2 {
  public static void main(String[] args) {
    C c = new C();
    if( args.length == 0 ){
      c.m();
    }
  }
}
~/github/test$ cat base/I1.java 
public interface I1 {
  default void m(){
    System.out.println("hello interface 1");
  }	
}
~/github/test$ cat base/I2.java 
public interface I2 {
}

We can compile and run the class using the command lines:

~/github/test$ javac -cp .:base C.java
~/github/test$ java -cp .:base C
hello interface 1

The directory compatible contains a version of the interface I2 that declares the method m() abstract, and for technical reasons it contains I1.java unaltered.

~/github/test$ cat compatible/I2.java 

public interface I2 {
  void m();
}

This can not be used to compile the class C:

~/github/test$ javac -cp .:compatible C.java 
C.java:1: error: C is not abstract and does not override abstract method m() in I2
public class C implements I1, I2 {
       ^
1 error

The error message is very precise. Even though we have the C.class from the previous compilation and if we compile the interfaces in the directory compatible we will have two interfaces that can still be used to run the class:

~/github/test$ javac compatible/I*.java
~/github/test$ java -cp .:compatible C
hello interface 1

The third directory, wrong contains a version of I2 that also defines the method m():

~/github/test$ cat wrong/I2.java 
public interface I2 {
  default void m(){
    System.out.println("hello interface 2");
  }
}

We should not even bother to compile it. Even though the method is double defined the class still can be executed so long as long it does not invoke the method, but it fails as soon as we try to invoke the method m(). This is what we use the command line argument for:

~/github/test$ javac wrong/*.java
~/github/test$ java -cp .:wrong C
Exception in thread "main" java.lang.IncompatibleClassChangeError: Conflicting default methods: I1.m I2.m
	at C.m(C.java)
	at C.main(C.java:5)
~/github/test$ java -cp .:wrong C x
~/github/test$

Conclusion

When you start to move your library to Java 8 and you modify your interfaces adding default implementations, you probably will not have problems. At least that is what Java 8 library developers hope adding functional methods to collections. Applications using your library are still relying on Java 7 libraries that do not have default methods. When different libraries are used and modified, there is a slight chance of conflict. What to do to avoid it?

Design your library APIs as before. Do not go easy relying on the possibility of default methods. They are last resort. Choose names wisely to avoid collision with other interfaces. We will learn how Java programming will develop using this feature.

Reference: Java 8 default methods: what can and can not do? from our JCG partner Peter Verhas at the Java Deep blog.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button