Translation of an article written by Peter Verhas dated April 2014. From the translator: the term " default method " has just appeared in Java and I'm not sure if there is an established translation into Russian for it. I'll use the term "default method", although I don't think it's ideal. I invite you to discuss a more successful translation.
What is the default method
Now, with the release of Java 8, you can add new methods to interfaces so that the interface remains compatible with the classes that implement it. This is very important if you are developing a library that is used by many programmers from Kyiv to New York. Before Java 8, if you defined an interface in a library, you couldn't add methods to it without risking that some application running your interface would break when it was updated. So, in Java 8 you can no longer be afraid of this? No you can not. Adding a default method to an interface may make some classes unusable. Let's first look at the good things about default methods. In Java 8, the method can be implemented directly in the interface. (Static methods in an interface can now also be implemented, but that's another story.) A method implemented in an interface is called a default method, and is denoted by the default keyword . If a class implements an interface, it can, but is not required to, implement the methods implemented in the interface. The class inherits the default implementation. This is why it is not necessary to modify classes when changing the interface they implement.Multiple inheritance?
Things get more complicated if a class implements more than one (say, two) interfaces, and they implement the same default method. Which method will the class inherit? The answer is none. In this case, the class must implement the method itself (either directly or by inheriting it from another class). The situation is similar if only one interface has a default method, and in the other the same method is abstract. Java 8 tries to be disciplined and avoid ambiguous situations. If methods are declared in more than one interface, then no default implementation is inherited by the class - you will get a compilation error. Although, you may not get a compilation error if your class is already compiled. Java 8 is not robust enough in this regard. There are reasons for this, which I don’t want to go into discussing (for example: the Java release has already been released and the time for discussions has long passed and in general, this is not the place for them).- Let's say you have two interfaces and a class implements both of them.
- One of the interfaces implements the default method m().
- You compile all the interfaces and the class.
- You change an interface that doesn't have an m() method by declaring it as an abstract method.
- You compile only the modified interface.
- Start the class.
- change the interface with the abstract m() method and add a default implementation.
- Compile the modified interface.
- Run class: error.
Example code
To demonstrate the above, I created a test directory for the C.java class and 3 subdirectories for the interfaces in the files I1.java and I2.java. The root directory for the test contains the source code for the C.java class. The base directory contains a version of the interfaces that are suitable for execution and compilation: interface I1 has a default method m(); The I2 interface does not yet have any methods. The class has a methodmain
so we can execute it to test it. It checks if there are any command line arguments, so we can easily execute it either with or without calling the 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 {
}
You can compile and run the class from the command line.
~/github/test$ javac -cp .:base C.java
~/github/test$ java -cp .:base C
hello interface 1
The compatible directory contains a version of the I2 interface that declares the m() method to be abstract, and also, for technical reasons, an unmodified copy of I1.java.
~/github/test$ cat compatible/I2.java
public interface I2 {
void m();
}
Such a set cannot be used to compile a C class:
~/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 accurate. However, we have C.class from a previous compilation and, if we compile the interfaces into the compatible directory, 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 version I2, which also declares the method m()
:
~/github/test$ cat wrong/I2.java
public interface I2 {
default void m(){
System.out.println("hello interface 2");
}
}
You don't even have to worry about compilation. Even though the method is declared twice, the class can still be used and run until the m() method is called. This is what we need 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$