At first glance, the default method brings many new features to the Java virtual machine's instruction set. Ultimately, library developers will be able to upgrade the API without introducing compatibility issues with client code. With default methods, any class that implements a library interface automatically adapts to the default methods introduced by the interface. Once the user updates the class he implements, it is easy to override the original default method with a more meaningful method. Even better, users can call the default implementation of the interface and add business logic when overriding the method.
So far, so good. However, adding default methods when creating an interface may make Java code incompatible. This can be easily understood from the example below. Let's assume that a library requires 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, similar to the above method of jointly using an interface and an adapter class, it was a very common design pattern in the Java programming language. . This adapter is usually provided by the library provider to save users of the library certain operations. However, if it is provided in the form of an interface, it is similar to allowing multiple inheritance.
We further assume that a user uses the following adapter:
class MyInput extends SimpleInputAdapter { @Override public void foo() { // do something ... } @Override public void bar() { super.bar(); // do something additionally ... } }
With this implementation, we can finally interact with the library. Notice how we override the bar method and add additional functionality to the default implementation.
What will happen if this library is ported to Java 8? First, the library will most likely deprecate the adapter class and use the default method to provide this functionality. Ultimately, the interface looks like this:
interface SimpleInput { void foo(); default void bar() { // some default behavior } }
Using this new interface, the user can update his code to use default methods instead of the original adapter class. The ultimate consequence of using an interface instead of an adapter class is that the class can extend other classes rather than a specific adapter. Now let's practice and transplant the MyInput class to use the default method. Since we can now extend other classes, we extend a third-party base class. We don't need to care about the role of this basic class here, we can assume that this is meaningful for our function.
class MyInput extends ThirdPartyBaseClass implements SimpleInput { @Override public void foo() { // do something ... } @Override public void bar() { SimpleInput.super.bar(); // do something additionally ... } }
In order to achieve similar functions to the original class, we use the new syntax of Java 8 to call the default method of the specified interface. At the same time, move some of the logic in our methods to the base class. At this point, you may be patting me on the shoulder and saying, this is a very good refactoring!
We have used this library quite successfully. However, maintainers need to add another interface to provide more functionality. This interface is replaced by the ComplexInput interface, which inherits from the SimpleInput interface and adds new methods. Because default methods are generally safe to add, the maintainers have overridden SimpleInput 's default method to provide a better default method. After all, this is a common thing to do with adapter classes.
interface ComplexInput extends SimpleInput { void qux(); @Override default void bar() { SimpleInput.super.bar(); // so complex, we need to do more ... } }
The new features brought such good results that the people maintaining ThirdPartyBaseClass also decided to rely on this library. To do this job, it implements the ComplexInput interface in ThirdPartyLibrary.
But what does this mean for the MyInput class? In order to implicitly implement the ComplexInput interface, you can inherit the ThirdPartyBaseClass class, but calling the default method of SimpleInput suddenly becomes illegal. As a result, the user's code fails to compile. This kind of call is now prohibited because Java considers it illegal to call the parent class's method from a non-direct subclass. You can only call this default method in ComplexInput, but this requires you to explicitly implement this interface in MyInput. For users of the library, this change is not expected!
What’s even more strange is that the Java runtime does not impose such restrictions. The JVM's validator allows a compiled class to call the SimpleInput::foo method, even if the class inherits the updated ThirdPartyBaseClass, thereby implicitly implementing ComplexClass. This restriction only exists in the compiler.
The above is the detailed content of Will Java 8 default methods break user code?. For more information, please follow other related articles on the PHP Chinese website!