Chapter 1 – Java 8 and Interface Enhancements
Introduction to Java 8
-Default Methods and Functional Interfaces
-Creating Default Methods
-Understanding the Importance of Default Methods
-Defining a Default Method
--Overriding Default Methods
-Using Inheritance with Default Methods
--Working with Single Inheritance
--Working with Multiple Inheritance
--Understanding how Diamond Inheritance Works
-Resolving Overridden Default Methods
-Using Default Methods
--Extending Existing Interfaces
--Using Default Methods to Supplement Adapter Classes
--Using Default Methods in Core Java Classes
--Using a Class to Support Default Methods
-Understanding the Difference between an Abstract Class and Interfaces
-Using Static Interface Methods
-Functional Interfaces
--Creating a Functional Interface
--Using the @FunctionalInterface Annotation
--Overriding Object Class Methods in a Functional Interface
--Using Functional Interfaces in the Core Libraries
-Conclusion
Introduction to Java 8
For Java to remain competitive it has to evolve. The enhancements to version 8 of Java introduce a number of new language features and packages. In this book we will examine most of these additions starting with an explanation of default methods and function interfaces. These new features, whilst useful in themselves, support other Java 8 features such as lambda expressions and stream. Interfaces are the focus of the first chapter.
One of the goals for Java 8 was to support and encourage the use of a functional style of programming. While this has not been completely achieved, the addition of lambda expressions certainly encourages this style. Lambda expressions are essentially anonymous functions. With Java 8 come a number of new function-like libraries designed to work with lambda expressions. Chapter 2 explores how to create and use these expressions.
Another goal of Java 8 is to provide better support for concurrent processing. Earlier techniques, while workable, can be error prone and hard to implement. The introduction of Streams to Java 8 moves some of the conceptual difficulties of concurrent programming away from the programmer and embeds them in streams. A stream can be thought of as a collection of elements to be processed either sequentially or concurrently. The programmer can specify what needs to be done and not worry about how it is done. In addition, there is support for internal iteration within the collection framework with the addition of the forEach method. We will see many applications of streams in Chapter 3.
Handling date and time has never been as clean as it could be in Java. This is rectified in Java 8 with the introduction of the java.time package and new classes to support time related tasks. The improvements introduced help make the execution of these tasks more readable and reliable. The formatting of time has also been improved. These enhancements are presented in Chapter 4.
There have been numerous other additions to Java. These will be discussed in Chapter 5. They range from virtual machine enhancements, which are not always readily visible to the typical programmer, to enhancements in how Java applications are annotated and documented. In addition, there are a few concurrency and character encoding updates. Also, the Nashorn JavaScript engine has been incorporated into Java which enables execution of JavaScript code from within a Java application.
Default Methods and Functional Interfaces
This chapter focuses on the various changes that have been made to interfaces and how they can be defined and used. It starts out with a discussion of default methods. These types of methods are an important aspect of Java 8 as they lay the groundwork for other enhancements such as lambda expressions and streams. We cover how default expressions are created and used both in old applications and in new ones.
Default methods are methods of an interface that have implementations. This means that classes that implement these types of interfaces do not have to implement default methods. Default methods have been added to existing interfaces such as those found in many collection classes. Since classes that implement such interfaces do not need to override those methods, existing code that uses these interfaces will still work properly.
As with earlier versions of Java, inheritance between interfaces is supported. However, default methods add a new dimension to inheritance and offer opportunities for overriding methods in both single and multiple inheritance situations.
Functional interfaces are interfaces that possess one, and only one, abstract method. These types of interfaces are also called Single Abstract Methods (SAM). While an interface can possess default methods, it can have only one abstract method. As demonstrated in Chapter 2, this type of interface provides the foundation for lambda expressions.
Creating Default Methods
Default methods are interface methods that have an implementation. They are declared using the default keyword. An example of a default method follows with a Drawable interface definition consisting of one default method and one abstract method:
interface Drawable {
public default void draw() {
System.out.println(
"The Drawable interface's draw method");
}
public abstract boolean hasBeenDrawn();
}
To illustrate the use of this interface we will use a Person class as defined below. The draw method does not need to be implemented but the hasBeenDrawn method does since it is abstract:
class Person implements Drawable {
public boolean hasBeenDrawn() {
return true;
}
}
From a main method we create an instance of the Person class and execute the draw method:
public static void main(String[] args) {
Person person = new Person();
person.draw();
}
When executed we get an output as follows:
The Drawable interface's draw method
The implementing class did not have to implement the default method in order to use it.
A default method can be added to an existing interface. These methods are sometimes called virtual extension methods. This term arises from the idea of extending an existing interface so that it can acquire new methods without breaking existing code. The interface is said to have been extended. Virtual extension methods are also called defender methods.
Note: I will use the term, default methods, since we use the keyword default for default methods (in a similar way to how we use the static keyword for static methods). It can be argued that the addition of default methods to an interface corrupts the contract implied by the interface concept. In some sense, interfaces are no longer “pure”. Whether this change is for the better can be debated. Regardless, the use of default methods provides an additional technique that is available to developers.
Understanding the Importance of Default Methods
Default methods were introduced for several reasons:
- Existing code does not have to be updated to implement a new method. This allows for interfaces to grow or be extended.
- To provide additional flexibility in the design of an inheritance hierarchy such as:
-- Providing additional options in the placement of functionality within an interface hierarchy
-- Supporting interfaces that mimic adapter class type functionality
- To support lambda expressions
Private variables are still not permitted in an interface. In the previous example, the abstract method, hasBeenDrawn, implies the potential use of a state-like variable. However, any support for state information will have to be provided by the implementer of the interface.
Defining a Default Method
Let’s examine the details of creating and using a default method. The default keyword is always used to declare a default method. Technically, the keyword would not seem to be necessary. After all, it is clear from the existence of the method’s body that it is not abstract. The main reason it is required is to make it obvious that the method is implemented.
This is analogous to the use of the abstract keyword to declare abstract methods and classes. While an abstract method is obvious from its lack of a body, and an abstract class is obvious from the presence of abstract methods, the use of these keywords clarifies the intent. For example, the abstract keyword is not required to declare an abstract method in an interface. We could have declared the previous abstract method as follows:
public interface Drawable {
…
public boolean hasBeenDrawn();
}
However, we will use the abstract keyword, explicitly, to state our intent clearly.
The keyword default can be used in two places in the method’s declaration. Place the keyword either before, or after, any scope modifier. A scope modifier, while normally desirable, is not required. Possible placements of the keyword default are illustrated below:
public default void draw(){…}
default public void draw(){…}
default void draw(){…}
The term, default method, should not be confused with the idea that it is executed by default if no other methods are provided. In fact, an interface can have multiple default methods as shown below:
interface Drawable {
default void draw() {
System.out.println(
"The Drawable interface's draw method");
}
default void drawInQuotes() {
System.out.println(
"\"The Drawable interface's drawInQuotes method\"");
}
public abstract boolean hasBeenDrawn();
}
Both of these methods can be used from an implementing class as shown below:
Person person = new Person();
person.draw();
person.drawInQuotes();
The output is as follows:
The Drawable interface's draw method
"The Drawable interface's drawInQuotes method"
Overriding Default Methods
While a class must override abstract methods to avoid becoming abstract, default methods do not have to be overridden. They can be overridden as needed to provide an alternative implementation. In the following implementation of the Person class, an overridden draw method is provided:
interface Drawable {
public default void draw() {
System.out.println(
"The Drawable interface's draw method");
}
}
class Person implements Drawable {
@Override
public void draw() {
System.out.println("The Person's draw method");
}
}
While the @Overrride annotation is not required, it is a good programming practice to include it. When the draw method is now invoked, the Person class’ implementation is executed as shown below:
Person person = new Person();
person.draw();
The output follows:
The Person's draw method
If we want to use the default method’s implementation, from the class that implements the interface, we use the name of the interface followed by the keyword super and then the method name - all separated with periods. For the draw method, this is done as illustrated below:
class Person implements Drawable {
@Override
public void draw() {
Drawable.super.draw();
System.out.println("The Person's draw method");
}
}
The output shown below is based on the previous invocation of the draw method against the Person instance:
The Drawable interface's draw method
The Person's draw method
When overriding a default method in the implementing class, the keyword default cannot be used. For example, the following will not compile cleanly:
class Person implements Drawable {
@Override
public default void draw() {
System.out.println("The Person's draw method");
}
}
An error message similar to the following will be generated:
modifier default not allowed here
Using Inheritance with Default Methods
Interfaces can inherit from each other. Extensive hierarchies are possible including those that use multiple inheritance. As a result, default methods can be overridden in several places in a hierarchy. In this section, we will illustrate various inheritance alternatives and explain how the run-time system determines which overridden method to use.
Working with Single Inheritance
As with earlier versions of Java, interfaces can be derived from another interface. This is illustrated, below, where the Displayable interface derives from the Drawable interface. In this initial version, no methods have been added to the Displayable interface.
interface Drawable {
public default void draw() {
System.out.println(
"The Drawable interface's draw method");
}
}
public interface Displayable extends Drawable {
}
The Person class implements the Displayable interface as shown below:
public class Person implements Displayable {
}
These relationships are shown in Figure 1: Inheritance and Implementation Hierarchy Example. The solid line represents inheritance while the dashed line shows the Person class implementing the Displayable interface.
Figure 1: Inheritance and Implementation Hierarchy Example
When the draw method is executed against a Person instance, which does not override the draw method, the Drawable’s draw method is executed as shown below:
Person person = new Person();
person.draw();
The output is shown here:
The Drawable interface's draw method
We can override the draw method in the Displayable interface as shown below. Notice the use of the default keyword. It is required when we override a default method in interfaces.
interface Displayable extends Drawable {
public default void draw() {
System.out.println(
"The Displayable interface's draw method");
}
}
If the default keyword is left out, we get the following error message:
interface abstract methods cannot have body
The output of the draw method now reflects the overridden method as shown below:
The Displayable interface's draw method
It is possible to make a default method abstract in a hierarchy chain. Consider a third interface called MoreDisplayable as declared below. The draw method is overridden as an abstract method.
interface MoreDisplayable extends Displayable {
@Override
public abstract void draw();
}
When the Person class implements the MoreDisplayable interface it must implement the draw method. If it doesn’t, then the class must be declared as abstract.
public class Person implements MoreDisplayable {
}
The following error message will be generated if the class is not declared as abstract:
Person is not abstract and does not override abstract method draw() in MoreDisplayable
Working with Multiple Inheritance
The multiple inheritance of interfaces has always been possible in Java. This type of inheritance is one of behavior. However, Java still does not support multiple inheritance between classes. That type of inheritance is often referred to as inheritance of state.
Interfaces can inherit from more than one interface. However, if two or more of the base interfaces have the same default method, then ambiguity is introduced which needs to be addressed. To demonstrate how this is handled, we will explore an inheritance hierarchy where a MoreDisplayable interface is derived from both a Displayable and a Colorable interface as illustrated in Figure 2: Multiple Inheritance between Interfaces.
Figure 2: Multiple Inheritance between Interfaces
The first two interfaces are shown below:
public interface Displayable extends Drawable {
@Override
default void draw(){
System.out.println(
"The Displayable interface's draw method");
}
}
public interface Colorable {
public default void draw(){
System.out.println(
"The Colorable interface's draw method");
}
}
A definition of the MoreDisplayable interface that extends both the Displayable and the Colorable interface is shown below. However, no implementation is provided for the draw method.
public interface MoreDisplayable extends Displayable,
Colorable {
}
This creates an ambiguous situation because the compiler is unable to determine which of the two base interfaces’ draw methods to use. This results in the following error message:
interface MoreDisplayable inherits unrelated defaults for draw() from types Displayable and Colorable
There are several ways of resolving this problem. One way is by providing an abstract declaration for the draw method as follows. This forces the implementing class to implement the abstract method.
public interface MoreDisplayable extends Displayable,
Colorable {
@Override
public abstract void draw();
}
Another approach is to use a concrete draw method:
public interface MoreDisplayable extends Displayable,
Colorable {
@Override
public default void draw() {
System.out.println(
"The MoreDisplayable interface's draw method");
}
}
If we want to use one of the base class’s draw methods we can explicitly call it as shown below:
public interface MoreDisplayable extends Displayable,
Colorable {
@Override
public default void draw() {
Displayable.super.draw();
}
}
Actually, both methods can be called if needed:
public interface MoreDisplayable extends Displayable,
Colorable {
@Override
public default void draw() {
Displayable.super.draw();
Colorable.super.draw();
}
}
The best solution is dependent on the needs of the application.
Understanding how Diamond Inheritance Works
Diamond inheritance occurs when one interface is the base interface for two other interfaces and a fourth interface is then derived from these two interfaces. Figure 3: Diamond Inheritance illustrates this situation where the Drawable interface serves as the base interface for the Displayable and the Colorable interfaces and the MoreDisplayable interface is derived from both the Displayable and the Colorable interfaces.
Figure 3: Diamond Inheritance
There are several places where the draw method can be declared. In the following set of declarations it is declared only in the Drawable interface:
public interface Drawable {
default void draw(){
System.out.println(
"The Drawable interface's draw method");
}
}
public interface Displayable extends Drawable { }
public interface Colorable extends Drawable { }
public interface MoreDisplayable extends Displayable,
Colorable { }
This will compile without errors. A similar situation occurs if a class implements both the Displayable and Colorable interfaces:
public class Busy implements Displayable, Colorable {
}
When an instance of Busy is created and the draw method is executed against this instance, the Drawable's draw method will be executed since it is the only one available. If the Displayable interface also implements the draw method, then its method will be used by the Busy class.
public interface Displayable extends Drawable {
@Override
default void draw(){
System.out.println(
"The Displayable interface's draw method");
}
}
If both the Displayable and Colorable interfaces implement the draw method, then we have the same ambiguous situation we had in the previous section. The compiler is unable to determine which draw method should be used. The Busy class will typically solve the problem by providing a draw implementation. One possible implementation is shown below:
public interface Colorable extends Drawable {
public default void draw(){
System.out.println(
"The Colorable interface's draw method");
}
}
public class Busy implements Displayable, Colorable {
@Override
public void draw() {
Displayable.super.draw();
Colorable.super.draw();
}
}
If only the Drawable and the Displayable interfaces implement the draw method, the use of the draw method against the Busy class may not work as expected. In the following example a Busy instance is assigned to a Colorable interface reference variable:
Colorable colorable = new Busy();
colorable.draw();
It might be expected that the Drawable’s draw method would be called. However, the output shown below indicates otherwise:
The Displayable interface's draw method
The object referenced by the colorable variable is a Busy object and since the Busy class’ most immediate implementation of the draw method comes from the Displayable interface, that implementation is used.
Resolving Overridden Default Methods
When a default method is overridden in multiple places in a hierarchy, the run-time system needs to determine which method to use. A set of rules are used to resolve which method to use. To illustrate these rules we will use Figure 4: Resolving Overridden Default Methods that demonstrates single and multiple inheritance between interfaces and single inheritance between classes:
Figure 4: Resolving Overridden Default Methods
A class hierarchy is represented by the hierarchy of the classes Object, Entity, and Person. The Person class implements the MoreDisplayable interface which inherits from both the Displayable and the Colorable interfaces. The Displayable and Colorable interfaces both inherit from the Drawable interface.
When the run-time system tries to determine which method to execute, it will choose the method in the following order:
- The immediate class’ method
- Methods of the base classes (including those of the Object class)
- The most immediate default method of a possible hierarchy of interfaces
To illustrate these rules we will show various places where the display method could be implemented (and the effect the implementation has) when used against an instance of the Person class. This is based on the draw method being executed against an instance of Person as shown below:
Person p = new Person();
p.draw();
Table 1: Resolution Rules Example illustrates which draw method is used depending on where it is implemented:
Table 1: Resolution Rules Example
If draw is implemented in | And is not implemented in | It will use the method in
Person | Not applicable | Person
Entity | Person class | Entity
Object | Not possible | Not possible
MoreDisplayable | Person or Entity classes | MoreDisplayable
Displayable | Any class and not MoreDisplayable or Colorable | Displayable
Colorable | Any class and not MoreDisplayable or Displayable | Colorable
Drawable | Any other class or interface | Drawable
If the method is implemented in the Person class, then that implementation will always be used. If the method is not implemented there, then it will look to its base classes to find it. It will use the one in the Entity class if present. Since the Object class does not implement the draw method it will not find an implementation there.
If the method is not available in the class hierarchy, then it searches through the interface hierarchy. It will use the first implementation it finds, starting with the most derived interface first. In this case it will check to see if the MoreDisplayable interface has implemented it. If not then it will use either implementation found in the Displayable or Colorable interfaces. If both of these classes possess an implementation, then a compile error will result if the MoreDisplayable interface does not specify which one to use. This situation was discussed in the previous section. If only the Drawable interface implements the draw method, then this implementation will be used.
As demonstrated earlier, multiple implementations of the draw method can be used if an overridden method calls its immediate class or interface implementation.
This selection process can be summarized as:
- Classes take precedence over interfaces, and
- More specific interfaces (most derived) take precedence over less specific interfaces.
Using Default Methods
Default methods can be used in many situations. This section identifies and illustrates many of these possible uses. These include extending existing interfaces and supplementing adapter classes.
Extending Existing Interfaces
When adding a new abstract method to an existing interface, classes that have already implemented the interface will break since they no longer implement the new method. However, adding a default method will allow new methods to be added without breaking previous implementations. Traditionally, when an interface is defined it is treated as a contract and is not expected to change.
However, should the interface need to change, then the availability of default methods means that the interface can change and that change will not affect existing code. This permits existing interfaces to continue to be useable when new default methods are added. Older, existing usage of the interfaces does not break.
Consider the following interface that has a single abstract method draw:
interface Drawable {
public void draw();
}
When a class such as Person implements that interface it only needs to implement the draw method:
class Person implements Drawable {
public void draw() {
System.out.println("The Person's draw method");
}
}
However, if we add another abstract method to the Drawable interface, such as a drawWithQuotes method as shown below, we will introduce errors into the Person class:
interface Drawable {
public void draw();
public void drawWithQuotes();
}
The error message will be:
Person is not abstract and does not override abstract method drawWithQuotes() in Drawable
We are not able to easily add new methods without breaking old code written prior to Java 8. If, instead, we use a default method in the Drawable interface as follows, the error goes away. Our code will compile and execute correctly.
interface Drawable {
public void draw();
public default void drawWithQuotes() { }
}
Using Default Methods to Supplement Adapter Classes
While classes such as the MouseMotionAdapter class have not been replaced with default method interface implementations, it is possible to provide similar functionality using default methods. As an example, consider a MouseMotionAdapter interface equivalent. A simplified version of the MouseMotionAdapter source code, as found in Java 8, is shown below:
public abstract class MouseMotionAdapter implements MouseMotionListener {
public void mouseDragged(MouseEvent e) {};
public void mouseMoved(MouseEvent e) {};
}
An “equivalent” interface could appear as follows:
public interface MouseMotionAdapterInterface extends MouseMotionListener {
public default void mouseDragged(MouseEvent e) { };
public default void mouseMoved(MouseEvent e) { };
}
As example of using the MouseMotionAdapter follows. This code will print the current x coordinate of the mouse as the mouse moves across the window.
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.addMouseMotionListener(new MouseMotionAdapter() {
public void mouseMoved(MouseEvent e) {
System.out.println(e.getX());
}
});
frame.setBounds(100, 100, 500, 300);
frame.setVisible(true);
}
To demonstrate the use of the MouseMotionAdapterInterface we will use the following code. The code is equivalent to the previous example except that an instance of the MouseMotionAdapterInterface interface is used instead of MouseMotionAdapter. Otherwise, the code behaves in the same way.
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.addMouseMotionListener(
new MouseMotionAdapterInterface() {
public void mouseMoved(MouseEvent e) {
System.out.println(e.getX());
}
});
frame.setBounds(100, 100, 500, 300);
frame.setVisible(true);
}
The major difference is found in any implementation provided by the default method. If needed, we can add default behavior to default methods such as code to support logging.
Using Default Methods in Core Java Classes
The addition of lambda expressions is a major change to Java 8. For the developer to take advantage of this feature and yet still use interfaces, such as those of the collection libraries, it is desirable to use default methods to extend the interfaces.
As an example, the default forEach method has been added to the java.lang.Iterable interface as shown below. This method accepts an object that implements the Consumer functional interface. The forEach method will iterate through each element of the list and apply the Consumer instance’s accept method against each element. The Objects class’ requireNonNull method, added with Java 7, validates that the action object is not null.
public interface Iterable<T> {
Iterator<T>iterator();
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
…
}
We can now use the accept method with new code without breaking existing code. In the following example the Person class has been modified with the addition of a name field and supporting methods:
public class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" + "name=" + name +
", age=" + age + '}';
}
}
An ArrayList, which implements the Iterable interface, can use the forEach method to iterate through the list as shown below. A list of persons is created. The forEach method is then executed against the list. This example uses a lambda expression for the argument of the forEach method. While the lambda expression is not explained here, the effect is that each person in the list will be displayed by calling its toString method.
ArrayList<Person> roster = new ArrayList();
roster.add(new Person("Ralph", 45));
roster.add(new Person("Tom", 22));
roster.add(new Person("Mary", 31));
roster.forEach(person -> System.out.println(person));
The output of this sequence follows:
Person{name=Ralph, age=45}
Person{name=Tom, age=22}
Person{name=Mary, age=31}
This is easier than using the for each statement.
The Collection interface’s parallelStream method is another example of a default method, as shown below. The concept of Streams will be discussed in detail in Chapter 3.
public interface Collection<E> extends Iterable<E> {
...
default Stream<E>parallelStream() {
returnStreamSupport.stream(spliterator(), true);
}
}
Programmers can take advantage of these default method additions without the need to modify existing code. In the case of streams, it has the potential advantage of enhanced performance on multi-core machines.
Using a Class to Support Default Methods
Here we will illustrate how a class and interface can work together to support a state. This version of the Drawable interface uses two abstract and two default methods: draw and isNotDrawable. The default methods are dependent on the implementation of the abstract methods of the implementing class. The getString method should return the string used by the draw method. The isDrawable and isNotDrawable methods are intended to determine if the subject is, in fact, drawable.
interface Drawable {
public abstract String getString();
default void draw() {
System.out.println(getString());
}
abstract boolean isDrawable();
default boolean isNotDrawable() {
return !isDrawable();
}
}
The following illustrates an implementation of the Person class where two abstract methods have been implemented:
class Person implements Drawable {
public String getString() {
return "Person";
}
public boolean isDrawable() {
return true;
}
}
To test this implementation we use the following:
Person person = new Person();
person.draw();
System.out.println(person.isNotDrawable());
The output follows:
Person
false
Understanding the Difference between an Abstract Class and Interfaces
Abstract classes and interfaces with default methods may appear to be the same but there are several differences between them. These are summarized in Table 2: Abstract Class versus Interface. A class can only extend one class while multiple inheritance is supported between interfaces. State information is contained in instance variables. These are not allowed in interfaces. While we haven’t discussed lambda expressions in detail, yet, an instance of a functional interface can be used with lambda expressions. This is not possible with abstract classes.
The decision to use an abstract class versus an interface is dependent on the needs of your application.
Table 2: Abstract Class versus Interface
Characteristic | Abstract Class | Interface
Supports multiple inheritance | No | Yes
Can have a constructor | Yes | No
Can have a state | Yes | No
Can be used with lambda expressions | No | Yes
Using Static Interface Methods
Static methods can be added to interfaces in Java 8. The following illustrates the use of a static method in the Drawable interface. In this example, the getStandardUnit method is intended to return the standard unit of measure.
enum Unit {pixel, inch, millimeter};
interface Drawable {
public default void draw(){
System.out.println(
"The Drawable interface's draw method");
}
public static Unit getStandardUnit() {
return Unit.pixel;
}
}
The following illustrates the use of this interface where the Shape class implements the Drawable interface:
class Shape implements Drawable {}
public class TestExample {
public TestExample() {
Shape shape = new Shape();
shape.draw();
System.out.println(Drawable.getStandardUnit());
}
}
The output follows:
The Drawable interface's draw method
Pixel
Though the Shape class implements the Drawable interface, it does not possess the static method getStandardUnit. The following illustrates an attempt to use it:
System.out.println(Shape.getStandardUnit());
This will result in the following syntax error message:
cannot find symbol
symbol: method getStandardUnit()
location: class Shape
The addition of static methods to interfaces eliminates the need to create a helper class whose sole purpose is to provide static methods. They can now be integrated into the interface. The following illustrates the creation of the Drawable interface and a helper class that would be needed if default methods were not used:
enum Unit {pixel, inch, millimeter};
public interface Drawable {
...
}
class DrawableHelper {
public static Unit getStandardUnit() {
return Unit.pixel;
}
}
We can then use the class as follows:
Shape shape = new Shape();
shape.draw();
System.out.println(DrawableHelper.getStandardUnit());
While it takes the same number of statements to use the static method, we had to create an additional class with this approach.
Functional Interfaces
Functional interfaces are interfaces that have one, and only one, abstract method. However, these interfaces can have any number of default methods. Since there is only one abstract method, the compiler knows that that method will be the one used in certain situations. The existence of a single abstract method enables them to be used in interesting and powerful ways as exemplified by lambda expressions. As we will see, since a functional interface has only one abstract method, a lambda expression that matches the abstract method, can be used easily. This abstract method is referred to as the functional method.
In this section, we will learn more about functional expressions including the use of the @FunctionalInterface notation and how they have been incorporated into the Java core libraries. Detailed discussion of lambda expressions is postponed until Chapter 2.
Technically, a functional interface has one and only one non-Object method. As discussed in the Overriding Object Class Methods in a Functional Interface section, it is possible to override an Object method as abstract thus introducing more than one abstract method in a functional interface.
Creating a Functional Interface
The following illustrates a simple functional interface. It consists of a single abstract method.
public interface Drawable {
public abstract void draw();
}
A functional interface can remain a functional interface and yet have one or more non-abstract methods as illustrated below:
public interface Drawable {
public abstract void draw();
public default boolean hasBeenDrawn() {
return true;
}
}
The abstract method of a functional interface is called the functional method. In the previous example, the draw method is the functional method.
A function descriptor is the method type of the functional method. It consists of the parameter types, return type, and any exceptions thrown by that functional method. In the previous example, its method type consists of a method with no parameters, no return type, and it does not throw an exception. It is about as simple as things get. As we will see in Chapter 2, a function descriptor is represented by a lambda expression. The lambda expression is then matched against a functional method.
Using the @FunctionalInterface Annotation
The @FunctionalInterface annotation is available to inform the compiler that the interface should contain a single abstract function. The following illustrates its use:
@FunctionalInterface
public interface Drawable {
public abstract void draw();
}
The next three Drawable interface declarations illustrate invalid functional interfaces. In the first two declarations, the interfaces do not possess an abstract method:
@FunctionalInterface
interface Drawable {
public default void draw() {
System.out.println(
"The Drawable interface's draw method");
}
}
@FunctionalInterface
public interface Drawable {
}
The error message generated for both of these examples is the same:
Unexpected @FunctionalInterface annotation
Drawable is not a functional interface
no abstract method found in interface Drawable
In this declaration, the interface has more than one abstract method:
@FunctionalInterface
interface Drawable {
public abstract String getValue();
public abstract booleanhasBeenDrawn ();
}
The error message generated is shown below:
Unexpected @FunctionalInterface annotation
Drawable is not a functional interface
multiple non-overriding abstract methods found in interface Drawable
The @FunctionalInterface annotation is not required. If it is not used with an interface that is a functional interface, the compiler will still treat it as a functional interface.
Overriding Object Class Methods in a Functional Interface
All interfaces implicitly declare abstract methods of Object class methods. It is also possible to explicitly declare an abstract method from the Object class in an interface. These methods do not count toward the number of abstract methods in an interface.
For example, although the java.util package’s Comparator interface is a functional interface, it has two abstract methods: a compare and an equals abstract method. The equals method effectively overrides the Object class’ equals method.
The following illustrates how the equals method is used to add a second abstract method to the Drawable interface:
@FunctionalInterface
public interface Drawable {
public abstract boolean hasBeenDrawn();
@Override
public abstract boolean equals(Object obj);
}
An interface cannot declare, as a default method, any of the methods of the Object class. This includes the commonly used equals, hashCode, and toString methods. In general, overriding a method of the Object class is not always a good idea nor is necessary.
Using Functional Interfaces in the Core Libraries
Functional interfaces have always been part of Java but have not been explicitly called functional interfaces. For example, interfaces such as ActionListener and Runnable, shown below, are functional interfaces:
public interface ActionListener extends EventListener {
public void actionPerformed(ActionEvent e);
}
public interface Runnable {
public abstract void run();
}
In Java 8 many new functional interfaces have been added. Several have been added to the new java.util.function package to address common programmer needs. For example, the Predicate interface is used to support test cases that evaluate to true or false. Its functional method is test. It has a single generic argument and returns a boolean value.
boolean test(T t);
A more detailed discussion of these functional interfaces is found in Chapter 2.
Conclusion
In this chapter, we have focused on the use of default methods and functional interfaces. Default methods are methods of an interface that have an implementation. This feature has been added to allow interfaces to grow over time without breaking existing uses of the interface. The keyword default is used to declare such a method.
With the addition of default methods, we have to consider how overridden default methods are handled in single and multiple inheritance situations. Class implementations take precedence over interface implementations. The most derived method’s default method takes precedence over base interface default method implementations.
Functional interfaces are interfaces that have one, and only one, abstract method. These are used to support lambda expressions. The abstract method of a functional interface is called the functional method. This method has a function description associated with it that provides a target signature used with lambda expression.
The @FunctionalInterface annotation has been added to Java 8 to indicate that an interface is intended to be a functional interface. However, a functional interface does not require this annotation.
In the next chapter we examine the use of functional interfaces and their use with lambda expressions. We will also explore the new java.util.function package that introduces a number of functional interfaces.
* * * * *