The default method adds a very good new feature to the JVM's instruction set. After using the default method, if a new method is added to the interface in the library, the user class that implements this interface can automatically obtain the default implementation of this method. Once the user wants to update his implementation class, just override this default method, and replace it with an implementation that makes more meaningful in a specific scenario. What's even better is that users can call the default implementation of the interface in the rewritten method to add some extra functions.
Everything is pretty good so far. However, adding default methods to existing Java interfaces may lead to code incompatibility . It's easy to understand by looking at an example. Suppose there is a library that requires the user to implement one of its interfaces as input:
interface SimpleInput { void foo(); void bar();} abstract class SimpleInputAdapter implements SimpleInput { @Override public void bar() { // some default behavior ... }}Before Java 8, the combination of the above interface and a corresponding adapter class was a very common pattern in the Java language. The class library developers provide an adapter to reduce the amount of encoding for library consumers. However, the purpose of providing this interface is actually to achieve a relationship similar to multiple inheritance.
Let's assume that a user uses this adapter:
class MyInput extends SimpleInputAdapter{ @Override public void foo() { // do something ... } @Override public void bar() { super.bar(); // do something additionally ... }}With this implementation, users can interact with the library. Note how this implementation overrides the bar method to add additional functionality to the default implementation.
So what would happen if this library was migrated to Java 8? First of all, this library is likely to discard the adapter class and migrate this function to the default method. In the end, this interface will look like this:
interface SimpleInput { void foo(); default void bar() { // some default behavior }}}With this new interface, the user has to update his code to use this default method instead of the adapter class. One of the great benefits of using a new interface instead of an adapter class is that users can inherit another class instead of this adapter class. Let's start by practicing and transform the MyInput class into using the default method. Since we can now inherit other classes, we will try to extend an additional third-party base class. What this base class does is not important here. Let's assume that this makes sense for our use case.
class MyInput extends ThirdPartyBaseClass implements SimpleInput { @Override public void foo() { // do something ... } @Override public void bar() { SimpleInput.super.foo(); // do something additionally ... }}In order to achieve the same function as the original class, we used the new Java 8 syntax to call the default methods of the interface. Similarly, we put the logic of myMethod into a base class MyBase. You can relax after pounding your shoulders. It was awesome after the refactoring!
This library we use has been greatly improved. However, maintenance personnel need to add another interface to implement some additional functionality. This interface is called CompexInput , which inherits the SimpleInput class and adds an additional method. Since it is generally believed that the default method can be added with confidence, the maintenance staff overridden the default method of the SimpleInput class and added some extra actions to provide the user with a better default implementation. After all, this is very common when using adapter classes:
interface ComplexInput extends SimpleInput { void qux(); @Override default void bar() { SimpleInput.super.bar(); // so complex, we need to do more ... }}This new feature looks very good, so the Maintainer of ThirdPartyBaseClass class also decided to use this library. To achieve this, he implemented the ThirdPartyBaseClass class to the ComplexInput interface.
But what does this mean for the MyInput class? Since it inherits the ThirdPartyBaseClass class, the ComplexInput interface is implemented by default. In this way, calling the default method of SimpleInput is not legal. The result is that the user's code cannot be compiled in the end. Also, this method is completely impossible to call, because Java considers this super-super method to call the indirect parent class illegal. You can only call the default method of the ComplexInput interface. However, this first requires you to explicitly implement this interface in the MyInput class. For users of this library, these changes are completely unexpected.
(Note: To put it simply, it is actually:
interface A { default void test() { }}interface B extends A { default void test() { }}public class Test implements B { public void test() { B.super.test(); //A.super.test(); Error }}Of course, if you write this, the user actively chose to implement the B interface. The example in the article introduced a base class, so a seemingly uninfluential change was made in the library and base class, but in fact it caused the user code to be unable to be compiled)
Strangely, Java does not distinguish this at runtime. The JVM's validator allows a compiled class to call the SimpleInput::foo method, although the loaded class inherits the updated version of ThirdPartyBaseClass and implicitly implements the ComplexInput interface. If you want to blame, you can only blame the compiler. (Note: The compiler and runtime behavior are inconsistent)
So what have we learned from it? Simply put, don't override the default method of the original interface in another interface. Don't rewrite it with another default method, and don't rewrite it with some abstract method. In short, you should be very cautious when using the default method. Although they make the interfaces of Java's existing collection libraries easier to improve, it allows you to make method calls in the inheritance structure of the class, which essentially increases complexity. Before Java 7, you just had to traverse the linear class hierarchy and look at the actual calling code. When you feel you really need it, use the default method.
The above is a detailed explanation of why you should use the default method of Java 8 with caution. I hope it will be helpful to everyone's learning.