• Tidak ada hasil yang ditemukan

A Peek into the default Methods

Dalam dokumen Book Functional Programming in Java (Halaman 92-95)

In the design we explored in the previous section we used the new default methods. default methods are not intrinsically tied to lambda expressions or the functional style of programming. However, many of the convenience methods in collections would not have been possible without them.

Interface evolution is the key motivation behind default methods. The API defined in the early ’90s was a good idea back then, but for the platform to stay relevant it needs to evolve. The default methods provide a nondisruptive path for that. Moving forward, when we design with interfaces we’ll likely use default methods. Let’s examine their behavior and how they intermix with classes.

The Java compiler follows a few simple rules to resolve default methods:

1. Subtypes automatically carry over the default methods from their supertypes.

2. For interfaces that contribute a default method, the implementation in a subtype takes precedence over the one in supertypes.

3. Implementations in classes, including abstract declarations, take prece- dence over all interface defaults.

A Peek into the default Methods

77

4. If there’s a conflict between two or more default method implementations, or there’s a default-abstract conflict between two interfaces, the inheriting class should disambiguate.

To get a better understanding of these rules, let’s create an example with default methods.

public interface Fly {

default void takeOff() { System.out.println("Fly::takeOff"); } default void land() { System.out.println("Fly::land"); } default void turn() { System.out.println("Fly::turn"); } default void cruise() { System.out.println("Fly::cruise"); } }

public interface FastFly extends Fly {

default void takeOff() { System.out.println("FastFly::takeOff"); } }

public interface Sail {

default void cruise() { System.out.println("Sail::cruise"); } default void turn() { System.out.println("Sail::turn"); } }

public class Vehicle {

public void turn() { System.out.println("Vehicle::turn"); } }

All the interfaces in this example have default methods. The FastFly interface extends from the Fly interface and overrides the takeOff() method, providing its own default implementation. FastFly also carries forward the other three methods of the Fly interface (rule 1). Any class or interface inheriting from FastFly will see the implementation of takeOff() in FastFly, and not the implementation in Fly (rule 2).

All three interfaces have implementations for the cruise() and turn() methods.

In addition, the Vehicle class implements the turn() method.

Let’s create a class that inherits these types.

public class SeaPlane extends Vehicle implements FastFly, Sail { private int altitude;

//...

public void cruise() {

System.out.print("SeaPlane::cruise currently cruise like: ");

if(altitude > 0)

FastFly.super.cruise();

else

Sail.super.cruise();

} }

SeaPlane extends Vehicle and implements the FastFly and Sail interfaces. Let’s take a closer look at the implementation of this class.

There appears to be a conflict for the turn() method, but that’s really not the case. Even though the turn() method is present in the interfaces, the implemen- tation in the superclass Vehicle takes precedence here (rule 3), so there’s no conflict to resolve.

However, the Java compiler will force us to implement the cruise() method in the SeaPlane class because the default implementations in the interfaces FastFly (derived from Fly) and Sail conflict (rule 4).

From within the overridden methods we can call back into the corresponding default methods. For example, from within the cruise() method, we can see how to call the default methods of both the FastFly and the Sail interfaces.

We can see the logic of why we’d need to specify the interface name, like FastFly or Sail, when invoking the default methods from within the overriding method.

At first glance the use of super may appear superfluous, but it’s required.

That’s how the Java compiler knows if we’re referring to a default method (when super is used) or a static method in the interface. In Java 8, interfaces can optionally have default methods and static methods, possibly with the same name.

To see the behavior of the default methods in action, let’s create an instance of SeaPlane and invoke the methods on it.

SeaPlane seaPlane = new SeaPlane();

seaPlane.takeOff();

seaPlane.turn();

seaPlane.cruise();

seaPlane.land();

Before running the code on the computer, we’ll run it mentally; let’s go over the code to ensure we’ve understood the rules.

The call to the takeOff() method should go to the implementation in the FastFly interface (rules 1 and 2). The implementation of the turn() method in Vehicle should be picked for the call to the turn() method on the SeaPlane, even though these are available on the interfaces (rule 3). Since we were forced to implement the cruise() method on the SeaPlane, that specific implementation of the method should be invoked for the call to cruise() (rule 4). Finally, the call to the land() method will land on the implementation in the Fly interface (rule 1).

We can now compare the output we got from the mental run of the code with the output from the run on the computer:

A Peek into the default Methods

79

FastFly::takeOff Vehicle::turn

SeaPlane::cruise currently cruise like: Sail::cruise Fly::land

We used default methods in interfaces, whereas in the past interfaces were allowed to have only abstract methods. Seeing this, it may seem that interfaces have evolved into abstract classes, but that’s not the case. Abstract classes can have state, but interfaces can’t—this eliminates the concerns of the

“diamond problem” of collision from multiple inheritance. Also, we can inherit (implement) a class from multiple interfaces, but we only inherit (extend) from at most one abstract class. The good old recommendation to favor interfaces over abstract classes where possible is still a nice rule to fol- low. And now, thanks to the ability to have default methods, interfaces are even more attractive and powerful than before.

Now that we understand the behavior of default methods, let’s shift our attention back to lambda expressions. So far in this chapter, we’ve seen the different forms the lambda expressions can take and the multiple design goals we were able to achieve using them. Next we’ll cover how they can influence a class’s interface.

Dalam dokumen Book Functional Programming in Java (Halaman 92-95)