Default methods - Effective Java 8 programming - Java 8 in Action: Lambdas, streams, and functional-style programming (2015)

Java 8 in Action: Lambdas, streams, and functional-style programming (2015)

Part III. Effective Java 8 programming

Chapter 9. Default methods

This chapter covers

· What default methods are

· Evolving APIs in a compatible way

· Usage patterns for default methods

· Resolution rules

Traditionally, a Java interface groups related methods together into a contract. Any class that implements an interface must provide an implementation for each method defined by the interface or inherit the implementation from a superclass. But this causes a problem when library designers need to update an interface to add a new method. Indeed, existing concrete classes (which may not be under their control) need to be modified to reflect the new interface contract. This is particularly problematic because the Java 8 API introduces many new methods on existing interfaces, such as the sort method on the List interface that you used in previous chapters. Imagine all the angry maintainers of alternative collection frameworks such as Guava and Apache Commons who now need to modify all the classes implementing the List interface to provide an implementation for the sort method too!

But don’t worry. Java 8 introduces a new mechanism to tackle this problem. It might sound surprising, but interfaces in Java 8 can now declare methods with implementation code; this can happen in two ways. First, Java 8 allows static methods inside interfaces. Second, Java 8 introduces a new feature called default methods that allows you to provide a default implementation for methods in an interface. In other words, interfaces can provide concrete implementation for methods. As a result, existing classes implementing an interface will automatically inherit the default implementations if they don’t provide one explicitly. This allows you to evolve interfaces nonintrusively. You’ve been using several default methods all along. Two examples you’ve seen are sort in the List interface and stream in the Collection interface.

The sort method in the List interface you saw in chapter 1 is new to Java 8 and is defined as follows:

default void sort(Comparator<? super E> c){

Collections.sort(this, c);

}

Note the new default modifier before the return type. This is how you can tell that a method is a default method. Here the sort method calls the Collections.sort method to perform the sorting. Thanks to this new method, you can sort a list by calling the method directly:

There’s something else that’s new in this code. Notice that you call the Comparator .naturalOrder method. It’s a new static method in the Comparator interface that returns a Comparator object to sort the elements in natural order (the standard alphanumerical sort).

The stream method in Collection you saw in chapter 4 looks like this:

default Stream<E> stream() {

return StreamSupport.stream(spliterator(), false);

}

Here the stream method, which you used extensively in previous chapters to process collections, calls the StreamSupport.stream method to return a stream. Notice how the body of the stream method is calling the method spliterator, which is also a default method of theCollection interface.

Wow! Are interfaces like abstract classes now? Yes and no; there are fundamental differences, which we explain in this chapter. But more important, why should you care about default methods? The main users of default methods are library designers. As we explain later, default methods were introduced to evolve libraries such as the Java API in a compatible way, as illustrated in figure 9.1.

Figure 9.1. Adding a method to an interface

In a nutshell, adding a method to an interface is the source of many problems; existing classes implementing the interface need to be changed to provide an implementation for the method. If you’re in control of the interface and all the implementations, then it’s not too bad. But this is often not the case. This is the motivation for default methods: they let classes automatically inherit a default implementation from an interface.

So if you’re a library designer, this chapter is important because default methods provide a means to evolve interfaces without causing modifications to existing implementations. Also, as we explain later in the chapter, default methods can help structure your programs by providing a flexible mechanism for multiple inheritance of "100%" style='width:100.0%'>

Static methods and interfaces

A common pattern in Java is to define both an interface and a utility companion class defining many static methods for working with instances of the interface. For example, Collections is a companion class to deal with Collection objects. Now that static methods can exist inside interfaces, such utility classes in your code can go away and their static methods can be moved inside an interface. These companion classes will remain in the Java API in order to preserve backward compatibility.

The chapter is structured as follows. We first walk you through a use case of evolving an API and the problems that can arise. We then explain what default methods are and how they can tackle the problems faced in the use case. Next, we show how you can create your own default methods to achieve a form of multiple inheritance in Java. We conclude with some more technical information about how the Java compiler resolves possible ambiguities when a class inherits several default methods with the same signature.

9.1. Evolving APIs

To understand why it’s difficult to evolve an API once it’s been published, let’s say for the purpose of this section that you’re the designer of a popular Java drawing library. Your library contains a Resizable interface that defines many methods a simple resizable shape must support:setHeight, setWidth, getHeight, getWidth, and setAbsoluteSize. In addition, you provide several out-of-the-box implementations for it such as Square and Rectangle. Because your library is so popular, you have some users who have created their own interesting implementations such as Ellipse using your Resizable interface.

A few months after releasing your API, you realize that Resizable is missing some features. For example, it would be nice if the interface had a setRelativeSize method that takes as argument a growth factor to resize a shape. You might say that it’s easy to fix: just add thesetRelativeSize method to Resizable and update your implementations of Square and Rectangle. Well, not so fast! What about all your users who created their own implementations of the Resizable interface? Unfortunately, you don’t have access to and can’t change their classes that implement Resizable. This is the same problem that the Java library designers face when they need to evolve the Java API. Let’s look at an example in detail to see the consequences of modifying an interface that’s already been published.

9.1.1. API version 1

The first version of your Resizable interface has the following methods:

public interface Resizable extends Drawable{

int getWidth();

int getHeight();

void setWidth(int width);

void setHeight(int height);

void setAbsoluteSize(int width, int height);

}

User implementation

One of your most loyal users decides to create his own implementation of Resizable called Ellipse:

public class Ellipse implements Resizable {

...

}

He’s created a game that processes different types of Resizable shapes (including his own Ellipse):

9.1.2. API version 2

After your library has been in use for a few months, you receive many requests to update your implementations of Resizable: Square, Rectangle, and so on to support the setRelativeSize method. So you come up with version 2 of your API, as shown here and illustrated in figure 9.2:

Figure 9.2. Evolving an API by adding a method to Resizable. Recompiling the application produces errors because it depends on the Resizable interface.

Problems for your users

This update of Resizable creates a few problems. First, the interface now requires an implementation of setRelativeSize. But the Ellipse implementation your user created doesn’t implement the method setRelativeSize. Adding a new method to an interface is binary compatible; this means existing class file implementations will still run without the implementation of the new method, if there’s no attempt to recompile them. In this case the game will still run (unless it’s recompiled) despite adding the method setRelativeSize to the Resizableinterface. Nonetheless, the user could modify the method Utils.paint in his game to use the method setRelativeSize because the paint method expects a list of Resizable objects as argument. If an Ellipse object is passed, an error will be thrown at run-time because thesetRelative-Size method isn’t implemented:

Exception in thread "main" java.lang.AbstractMethodError:

lambdasinaction.chap9.Ellipse.setRelativeSize(II)V

Second, if the user tries to rebuild his entire application (including Ellipse), he’ll get the following compile error:

lambdasinaction/chap9/Ellipse.java:6: error: Ellipse is not abstract and does not override abstract method setRelativeSize(int,int) in Resizable

Consequently, updating a published API creates backward incompatibilities. This is why evolving existing APIs, such as the official Java Collections API, causes problems for users of the APIs. There are alternatives to evolving an API, but they’re poor choices. For example, you could create a separate version of your API and maintain both the old and the new versions, but this is inconvenient for several reasons. First, it’s more complex for you to maintain as a library designer. Second, your users may have to use both versions of your API in the same codebase, which impacts memory space and loading time because more class files are required for their projects.

This is where default methods come to the rescue. They let library designers evolve APIs without breaking existing code because classes implementing an updated interface automatically inherit a default implementation.[1]

1 See https://blogs.oracle.com/darcy/entry/kinds_of_compatibility.

Different types of compatibilities: binary, source, and behavioral

There are three main kinds of compatibility when introducing a change to a Java program: binary, source, and behavioral compatibilities.1 You saw that adding a method to an interface is binary compatible but results in a compiler error if the class implementing the interface is recompiled. It’s good to know the different kinds of compatibilities, so let’s examine them in more detail.

Binary compatibility means existing binaries running without errors continue to link (which involves verification, preparation, and resolution) without error after introducing a change. For example, just adding a method to an interface is binary compatible because if it’s not called, existing methods of the interface can still run without problems.

In its simplest form, source compatibility means an existing program will still compile after introducing a change. For example, adding a method to an interface isn’t source compatible; existing implementations won’t recompile because they need to implement the new method.

Finally, behavioral compatibility means running a program after a change with the same inputs results in the same behavior. For example, adding a method to an interface is behavioral compatible because the method is never called in the program (or it gets overridden by an implementation).

9.2. Default methods in a nutshell

You’ve seen how adding methods to a published API disrupts existing implementations. Default methods are a new feature added in Java 8 to help evolve APIs in a compatible way. An interface can now contain method signatures for which an implementing class doesn’t provide an implementation. So who implements them? The missing method bodies are given as part of the interface (hence default implementations) rather than in the implementing class.

So how do you recognize a default method? It’s very simple. It starts with a default modifier and contains a body just like a method declared in a class. For example, in the context of a collection library, you could define an interface Sized with one abstract method size and a default method isEmpty as follows:

Now any class that implements the Sized interface will automatically inherit the implementation of isEmpty. Consequently, adding a method to an interface with a default implementation isn’t a source incompatibility.

Let’s go back to our initial example of the Java drawing library and your game. Concretely, to evolve your library in a compatible way (which means the users of your library don’t have to modify all their classes that implement Resizable), use a default method and provide a default implementation for setRelativeSize:

default void setRelativeSize(int wFactor, int hFactor){

setAbsoluteSize(getWidth() / wFactor, getHeight() / hFactor);

}

Because interfaces can now have methods with implementation, does that mean multiple inheritance has arrived in Java? What happens if an implementing class also defines the same method signature or if default methods can be overridden? Don’t worry about these issues for now; there are a few rules to follow and mechanisms available for you to deal with these issues. We explore them in detail in section 9.5.

You may have guessed that default methods are used extensively in the Java 8 API. You saw in the introduction of this chapter that the stream method in the Collection interface we used extensively in previous chapters is a default method. The sort method in the List interface is also a default method. Many of the functional interfaces we presented in chapter 3 such as Predicate, Function, and Comparator also introduced new default methods such as Predicate.and or Function.andThen (remember, a functional interface contains only one abstract method; default methods are non-abstract methods).

Abstract classes vs. interfaces in Java 8

So what’s the difference between an abstract class and an interface? They both can contain abstract methods and methods with a body.

First, a class can extend only from one abstract class, but a class can implement multiple interfaces.

Second, an abstract class can enforce a common state through instance variables (fields). An interface can’t have instance variables.

To put your knowledge of default methods to use, have a go at Quiz 9.1.

Quiz 9.1: removeIf

For this quiz, pretend you’re one of the masters of the Java language and API. You’ve received many requests for a removeIf method to use on ArrayList, TreeSet, LinkedList, and all other collections. The removeIf method should remove all elements from a collection that match a given predicate. Your task in this quiz is to figure out the best way to enhance the Collections API with this new method.

Answer:

What’s the most disruptive way to enhance the Collections API? You could copy and paste the implementation of removeIf in each concrete class of the Collections API, but that would be a crime to the Java community. What else can you do? Well, all of the Collection classes implement an interface called java.util.Collection. Great; can you add a method there? Yes; you just learned that default methods are a way to add implementations inside an interface in a source-compatible way. And all classes implementing Collection (including classes from your users that aren’t part of the Collections API) will be able to use the implementation of removeIf. The code solution for removeIf is as follows (which is roughly the implementation in the official Java 8 Collections API). It’s a default method inside the Collection interface:

default boolean removeIf(Predicate<? super E> filter) {

boolean removed = false;

Iterator<E> each = iterator();

while(each.hasNext()) {

if(filter.test(each.next())) {

each.remove();

removed = true;

}

}

return removed;

}

9.3. Usage patterns for default methods

You’ve seen how default methods can be useful to evolve a library in a compatible way. Is there anything else you can do with them? You can create your own interfaces that have default methods too. You may want to do this for two use cases that we explore in this section: optional methodsand multiple inheritance of behavior.

9.3.1. Optional methods

It’s likely you’ve come across classes that implement an interface but leave empty some method implementations. Take, for example, the Iterator interface. It defines hasNext and next but also the remove method. Prior to Java 8, remove was often ignored because the user decided not to use that capability. As a result, many classes implementing Iterator have an empty implementation for remove, which results in unnecessary boilerplate code.

With default methods, you can provide a default implementation for such methods, so concrete classes don’t need to explicitly provide an empty implementation. For example, the Iterator interface in Java 8 provides a default implementation for remove as follows:

interface Iterator<T> {

boolean hasNext();

T next();

default void remove() {

throw new UnsupportedOperationException();

}

}

Consequently, you can reduce boilerplate code. Any class implementing the Iterator interface doesn’t need to declare an empty remove method anymore to ignore it, because it now has a default implementation.

9.3.2. Multiple inheritance of behavior

Default methods enable something that wasn’t possible in an elegant way before: multiple inheritance of behavior. This is the ability of a class to reuse code from multiple places, as illustrated in figure 9.3.

Figure 9.3. Single inheritance vs. multiple inheritance

Remember that classes in Java can inherit from only one other class, but classes have always been allowed to implement multiple interfaces. To confirm, here’s how the class ArrayList is defined in the Java API:

Multiple inheritance of types

Here, ArrayList is extending one class and implementing six interfaces. As a result, an ArrayList is a direct subtype of seven types: AbstractList, List, RandomAccess, Cloneable, Serializable, Iterable, and Collection. So in a sense we already have multiple inheritance of types.

Because interface methods can have implementations in Java 8, classes can inherit behavior (implementation code) from multiple interfaces. Let’s explore an example to see how you can use this capability to your benefit. Keeping interfaces minimal and orthogonal lets you achieve great reuse and composition of behavior inside your codebase.

Minimal interfaces with orthogonal functionalities

Let’s say you need to define several shapes with different characteristics for the game you’re creating. Some shapes should be resizable but not rotatable; some should be rotatable and moveable but not resizable. How can you achieve great code reuse?

You can start by defining a standalone Rotatable interface with two abstract methods, setRotationAngle and getRotationAngle. The interface also declares a default rotateBy method that can be implemented using the setRotationAngle and get-RotationAngle methods as follows:

This technique is somewhat related to the template design pattern where a skeleton algorithm is defined in terms of other methods that need to be implemented.

Now, any class that implements Rotatable will need to provide an implementation for setRotationAngle and getRotationAngle but will inherit the default implementation of rotateBy for free.

Similarly, you can define two interfaces, Moveable and Resizable, that you saw earlier. They both contain default implementations. Here’s the code for Moveable:

public interface Moveable {

int getX();

int getY();

void setX(int x);

void setY(int y);

default void moveHorizontally(int distance){

setX(getX() + distance);

}

default void moveVertically(int distance){

setY(getY() + distance);

}

}

And here’s the code for Resizable:

public interface Resizable {

int getWidth();

int getHeight();

void setWidth(int width);

void setHeight(int height);

void setAbsoluteSize(int width, int height);

default void setRelativeSize(int wFactor, int hFactor){

setAbsoluteSize(getWidth() / wFactor, getHeight() / hFactor);

}

}

Composing interfaces

You can now create different concrete classes for your game by composing these interfaces. For example, monsters can be moveable, rotatable, and resizable:

The Monster class will automatically inherit the default methods from the Rotatable, Moveable, and Resizable interfaces. In this case, Monster inherits the implementations of rotateBy, moveHorizontally, moveVertically, and setRelativeSize.

You can now call the different methods directly:

Say you now need to declare another class that’s moveable and rotatable but not resizable, such as the sun. There’s no need to copy and paste code; you can reuse the default implementations from the Moveable and Rotatable interfaces as shown here. Figure 9.4 illustrates the UML diagram of this scenario:

Figure 9.4. Multiple behavior composition

Here’s another advantage of defining simple interfaces with default implementations like the ones for your game. Let’s say you need to modify the implementation of moveVertically to make it more efficient. You can now change its implementation directly in the Moveable interface, and all classes implementing it will automatically inherit the code (provided they didn’t implement the method themselves)!

Inheritance considered harmful

Inheritance shouldn’t be your answer to everything when it comes down to reusing code. For example, inheriting from a class that has 100 methods and fields just to reuse one method is a bad idea, because it adds unnecessary complexity. You’d be better off using delegation: create a method that calls directly the method of the class you need via a member variable. This is why you’ll sometime find classes that are declared “final” intentionally: they can’t be inherited from to prevent this kind of antipattern or have their core behavior messed with. Note that sometimes finalclasses have a place; for example, String is final because we don’t want anybody to be able to interfere with such core functionality.

The same idea is applicable to interfaces with default methods. By keeping your interface minimal, you can achieve greater composition because you can select only the implementations you need.

You’ve seen that default methods are useful for many usage patterns. But here’s some food for thought: what if a class implements two interfaces that have the same default method signature? Which method is the class allowed to use? We explore this problem in the next section.

9.4. Resolution rules

As you know, in Java a class can extend only one parent class but implement multiple interfaces. With the introduction of default methods in Java 8, there’s the possibility of a class inheriting more than one method with the same signature. Which version of the method should be used? Such conflicts will probably be quite rare in practice, but when they do occur there must be rules that specify how to deal with the conflict. This section explains how the Java compiler resolves such potential conflicts. We aim to answer questions such as “In the code that follows, which hellomethod is C calling?” Note that the examples that follow are intended to explore problematic scenarios; it doesn’t mean such scenarios will happen frequently in practice:

In addition, you may have heard of the diamond problem in C++ where a class can inherit two methods with the same signature. Which one gets chosen? Java 8 provides resolution rules to solve this issue too. Read on!

9.4.1. Three resolution rules to know

There are three rules to follow when a class inherits a method with the same signature from multiple places (such as another class or interface):

1. Classes always win. A method declaration in the class or a superclass takes priority over any default method declaration.

2. Otherwise, sub-interfaces win: the method with the same signature in the most specific default-providing interface is selected. (If B extends A, B is more specific than A).

3. Finally, if the choice is still ambiguous, the class inheriting from multiple interfaces has to explicitly select which default method implementation to use by overriding it and calling the desired method explicitly.

We promise, these are the only rules you need to know! Let’s now look at some examples.

9.4.2. Most specific default-providing interface wins

Let’s revisit our example from the beginning of this section where C implements both B and A, which define a default method called hello. In addition, B extends A. Figure 9.5 provides a UML diagram for the scenario.

Figure 9.5. The most specific default-providing interface wins.

Which declaration of the hello method will the compiler use? Rule 2 says that the method with the most specific default-providing interface is selected. Because B is more specific than A, the hello from B is selected. Consequently the program will print “Hello from B.”

Now, consider what would happen if C were inheriting from D as follows (illustrated in figure 9.6):

Figure 9.6. Inheriting from a class and implementing two interfaces

Rule 1 says that a method declaration in the class takes priority. But D doesn’t override hello; it implements interface A. Consequently, it has a default method from interface A. Rule 2 says that if there are no methods in the class or superclass, then the method with the most specific default-providing interface is selected. The compiler therefore has the choice between the hello method from interface A and the hello method from interface B. Because B is more specific, the program will print “Hello from B” again. To check your understanding of the resolution rules, try Quiz 9.2.

Quiz 9.2: Remember the resolution rules

For this quiz, let’s reuse the previous example except that D explicitly overrides the hello method from A. What do you think will get printed?

public class D implements A{

void hello(){

System.out.println("Hello from D");

}

}

public class C extends D implements B, A {

public static void main(String... args) {

new C().hello();

}

}

Answer:

The program will print “Hello from D” because a method declaration from a superclass has priority, as stated by rule 1.

Note that if D was declared as follows,

public abstract class D implements A {

public abstract void hello();

}

then C would be forced to implement the method hello itself, even though default implementations exist elsewhere in the hierarchy.

9.4.3. Conflicts and explicit disambiguation

The examples you’ve seen so far could be resolved using the first two resolution rules. Let’s say now that B doesn’t extend A anymore (illustrated in figure 9.7):

Figure 9.7. Implementing two interfaces

public interface A {

void hello() {

System.out.println("Hello from A");

}

}

public interface B {

void hello() {

System.out.println("Hello from B");

}

}

public class C implements B, A { }

Rule 2 doesn’t help you now because there’s no more-specific interface to select. Both hello methods from A and B could be valid options. Thus, the Java compiler will produce a compile error because it doesn’t know which method is more suitable: “Error: class C inherits unrelated defaults for hello() from types B and A.”

Resolving the conflict

There aren’t many solutions to resolve the conflict between the two possible valid methods; you have to explicitly decide which method declaration you want C to use. To do this, you can override the hello method in class C and then in its body explicitly call the method you wish to use. Java 8 introduces the new syntax X.super.m(...) where X is the superinterface whose method m you want to call. For example, if you want C to use the default method from B, it looks like this:

Have a go at Quiz 9.3 to investigate another related tricky case.

Quiz 9.3: Almost the same signature

For this quiz, assume interfaces A and B are declared as follows:

public interface A{

default Number getNumber(){

return 10;

}

}

public interface B{

default Integer getNumber(){

return 42;

}

}

And class C is declared as follows:

public class C implements B, A {

public static void main(String... args) {

System.out.println(new C().getNumber());

}

}

What will the program print?

Answer:

C can’t distinguish which method of A or B is more specific. This is why class C won’t compile.

9.4.4. Diamond problem

Let’s consider a final scenario that sends shivers through the C++ community:

Figure 9.8 illustrates the UML diagram for this scenario. It’s called a diamond problem because the shape of the diagram resembles a diamond. So what default method declaration does D inherit—the one from B or the one from C? There’s actually only one method declaration to choose from. Only A declares a default method. Because the interface is a superinterface of D, the code will print “Hello from A.”

Figure 9.8. The diamond problem

Now what happens if B also has a default hello method with the same signature? Rule 2 says that you select the most specific default-providing interface. Because B is more specific than A, the default method declaration from B will be selected. If both B and C declare a hello method with the same signature, you have a conflict and need to solve it explicitly, as we showed earlier.

Just as a side note, you may be wondering what happens if you add an abstract hello method (one that’s not default) in interface C as follows (still no methods in A and B):

public interface C extends A {

void hello();

}

The new abstract hello method in C takes priority over the default hello method from interface A because C is more specific. Therefore, class D now needs to provide an explicit implementation for hello; otherwise the program won’t compile.

C++ diamond problem

The diamond problem is more complicated in C++. First, C++ allows multiple inheritance of classes. By default, if a class D inherits from classes B and C, and classes B and C both inherit from A, then class D will actually have access to a copy of a B object and a copy of a C object. As a result, uses of methods from A have to be explicitly qualified: are they coming from B or are they coming from C? In addition, classes have state, so modifying member variables from B isn’t reflected on the copy of the C object.

You’ve seen that the default method’s resolution mechanism is pretty simple if a class inherits from several methods with the same signature. You just need to follow three rules systematically to solve all possible conflicts:

· First, an explicit method declaration in the class or a superclass takes priority over any default method declaration.

· Otherwise, the method with the same signature in the most specific default-providing interface is selected.

· Finally, if there’s still a conflict, you have to explicitly override the default methods and choose which one your class should use.

9.5. Summary

Following are the key concepts you should take away from this chapter:

· Interfaces in Java 8 can have implementation code through default methods and static methods.

· Default methods start with a default keyword and contain a body like class methods do.

· Adding an abstract method to a published interface is a source incompatibility.

· Default methods help library designers evolve APIs in a backward-compatible way.

· Default methods can be used for creating optional methods and multiple inheritance of behavior.

· There are resolution rules to resolve conflicts when a class inherits from several default methods with the same signature.

· A method declaration in the class or a superclass takes priority over any default method declaration. Otherwise, the method with the same signature in the most specific default-providing interface is selected.

· When two methods are equally specific, a class can explicitly override a method and select which one to call.