Functional Interfaces – Functional-Style Programming

13.1 Functional Interfaces

Functional interfaces and lambda expressions together facilitate behavior parameterization (p. 691), a powerful programming paradigm that allows code representing behavior to be passed around as values, and executed when the abstract method of the functional interface is invoked. This approach is scalable, requiring only a lambda expression to implement the functional interface.

Declaring a Functional Interface

A functional interface has exactly one abstract method. This abstract method is called the functional method of that interface. Like any other interface, a functional interface can declare other interface members, including static, default, and private methods (§5.6, p. 237). Here we concentrate on the single abstract method of a functional interface. This single abstract method, like all abstract methods in an interface, is implicitly abstract and public.

The StrPredicate interface shown below has only one abstract method and is, by definition, a functional interface.

Click here to view code image

@FunctionalInterface                             // Annotation.
interface StrPredicate {
  boolean test(String str);                      // Sole public abstract method.
}

The annotation @FunctionalInterface (§25.5, p. 1579) is optional when defining functional interfaces. If the annotation is specified, the compiler will issue an error if the declaration violates the definition of a functional interface, and the Javadoc tool will also automatically generate an explanation about the functional nature of the interface. In the absence of this annotation, there is no clue from the compiler to assert that an interface is a functional interface.

The CharSeqPredicate interface declaration below has two abstract methods, albeit they are overloaded, but it is not a functional interface.

Click here to view code image

@FunctionalInterface
interface CharSeqPredicate {                              // Compile-time error!
  boolean test(String str);                               // Abstract method.
  boolean test(StringBuilder sb);                         // Abstract method.
}

The functional interface NewStrPredicate below declares only one abstract method at (1). In addition, it provides the implementations of one default and one static method at (2) and (3), respectively.

Click here to view code image

@FunctionalInterface
interface NewStrPredicate {
  boolean test(String str);                                 // (1) Abstract method
  default void msg(String str) { System.out.println(str); } // (2) Default method
  static void info() { System.out.println(“Testing!”); }    // (3) Static method
}

An interface can provide explicit public abstract method declarations for any of the three non-final public instance methods in the Object class (equals(), hashCode(), toString()). Including these methods explicitly in an interface should not be attempted, unless there are compelling reasons for doing so. These methods are automatically implemented by every class that implements an interface, since every class directly or indirectly inherits from the Object class. Therefore, such method declarations are not considered abstract in the definition of a functional interface.

In the Funky interface below, the first three abstract method declarations are those of the non-final public methods from the Object class. As explained above, these methods do not count toward the definition of a functional interface. Effectively, the Funky interface declares only one abstract method given by the last abstract method declaration.

Click here to view code image

@FunctionalInterface
interface Funky {
  @Override int hashCode();                               // From Object class
  @Override boolean equals(Object obj);                   // From Object class
  @Override String toString();                            // From Object class
  boolean doTheFunk(Object obj);                          // Abstract method
}

The Java SE 17 platform API provides general-purpose functional interfaces in the java.util.function package that are discussed later (p. 695). Partial declaration of one such functional interface is shown below. It is the generic version of the StrPredicate functional interface defined earlier. The test() method of the Predicate<T> functional interface defines a boolean-valued predicate and evaluates it on the given object.

Click here to view code image

@FunctionalInterface
interface Predicate<T> {
  boolean test(T element);                                // Functional method.

  // …
}

Just like any other interface, a functional interface can be implemented by a class. The following generic method takes an object of type T and a Predicate<T> and determines whether the object satisfies the predicate.

Click here to view code image

public static <T> boolean testPredicate(T object, Predicate<T> predicate) {
  return predicate.test(object);
}

Below are two implementations of the parameterized functional interface Predicate<String> using a concrete class and an anonymous class, respectively.

Click here to view code image

// Class implementation of Predicate<String>.
class PredicateTest implements Predicate<String> {
  public boolean test(String str) {
    return str.startsWith(“A”);               // (1)
  }
}
// An anonymous class implementation of Predicate<String>.
static Predicate<String> testLength = new Predicate<>() {
  public boolean test(String str) {
    return str.length() < 4;                  // (2)
  }
};
// Client code:
System.out.println(testPredicate(“Anna”, new PredicateTest()));   // true
System.out.println(testPredicate(“Anna”, testLength));            // false

Note that for any combination of an object type and a predicate criteria, the Predicate<T> interface has to be implemented by a new class (or a new anonymous class) in order to utilize the testPredicate() method. Looking at the two implementations of the Predicate<String> above, it is essentially the expressions in the respective return statements at (1) and (2) that are different. This is where lambda expressions come in, allowing more concise implementation of the abstract method of a functional interface, without requiring all the boilerplate code.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *