11.2. Functional Interfaces

11.2.1. What is a Functional Interface?

Since Lambda Expressions are used to implement functional interfaces, it is important to first fully understand functional interfaces.

In Java, a functional interface is an interface that has just one abstract method aside from the methods of the Object class, and thus represents a single function contract. In other words, they are a guarantee that implementing classes contain an implementation of that one abstract method.

11.2.2. Identifying a Functional Interface

Test Yourself

Is the PaymentProcessor Interface from chapter 5 a functional interface? Why or why not?

Test Yourself Solution (don't open until answering the question above)

No. PaymentProcessor has two abstract methods, so we cannot implement it with a lambda expression.

The code below defines a functional interface called DiscountStrategy. In the next section, we will show how to implement this interface using a named class (class-based approach) like we did in the Interfaces Chapter and compare that approach to implementing the interface with a lambda expression.

public interface DiscountStrategy {
   /**
    * Applies this discount strategy to the specified
    * {@code originalPrice} and returns the adjusted
    * price.
    *
    * @param originalPrice the original price of the item.
    * @return the discounted price.
    */
   double apply(double originalPrice);
} // apply

Notice that the interface only contains one abstract method, apply, so it qualifies as a functional interface.

11.2.3. Implementing an Interface

Before we discuss any new syntax for implementing functional interfaces, it is important to note that a functional interface is still an interface and can be implemented using the same exact approach we learned earlier this semester. The five-step process described below summarizes that traditional approach to implementing an interface in Java:

  1. Sign the contract: Create a class in its own .java file and include the appropriate implements clause in the class declaration.

    Code Example
    Listing 11.1 PercentOffDiscount has signed the contract
    public class PercentOffDiscount implements DiscountStrategy {
        // ...
    } // PercentOffDiscount
    
  2. Meet the minimum requirements: Override the abstract methods from the interface so that the code compiles.

    Code Example

    The minimum requirement is simply to override the abstract method so the class will compile. At this stage, we can provide a placeholder implementation that satisfies the compiler.

    Note: The implementation doesn't need to be _correct_ to satisfy the compiler. Here, we just throw an exception indicating that the method is not complete.

     1public class PercentOffDiscount implements DiscountStrategy {
     2
     3    /**
     4     * Minimum implementation to satisfy compiler.
     5     */
     6    @Override
     7    public double apply(double originalPrice) {
     8        throw new UnsupportedOperationException("apply not yet implemented");
     9    } // apply
    10
    11} // PercentOffDiscount
    
  3. Meet the full requirements: Ensure that each overridden method aligns with the expectations outlined in the interface documentation.

    Code Example

    In this step, we replace the placeholder implementation with the actual implementation that fulfills the interface contract. Here, we compute the discounted price by subtracting the percentage from the original price. This is where the class’s behavior aligns with what the interface promises.

     1public class PercentOffDiscount implements DiscountStrategy {
     2
     3    private double percent;
     4
     5    public PercentOffDiscount(double percent) {
     6        this.percent = percent;
     7    } // PercentOffDiscount
     8
     9    /**
    10     * Applies the percentage discount to the original price.
    11     *
    12     * @param originalPrice the original price
    13     * @return the discounted price
    14     */
    15    @Override
    16    public double apply(double originalPrice) {
    17        return originalPrice * (1.0 - percent);
    18    } // apply
    19
    20} // PercentOffDiscount
    
  4. Instantiate: Create one or more objects of the class.

    Code Example

    Once the implementing class is complete, we can create objects using its constructor. Each object can have different internal state, such as a different discount percentage. This step demonstrates reusability and configuration via constructors.

    Note: This code is written in a driver program (likely in a separate class):

    1DiscountStrategy tenPercent = new PercentOffDiscount(0.10);
    2DiscountStrategy twentyPercent = new PercentOffDiscount(0.20);
    
  5. Interact using the interface: When interacting with the objects, use a variable with the interface as its type so that your code works with any object (of a class) that implements the interface and not just objects of the new class you created.

    Code Example

    Finally, we use the interface type when working with the objects. This allows the code to be flexible, since any implementation of DiscountStrategy can be used interchangeably. Here we apply the discounts to a price and can even pass the objects to methods that work with the interface type.

     1/**
     2 * Demonstrates applying different discounts using the DiscountStrategy interface.
     3 */
     4 public class DiscountDemo {
     5
     6     public static void main(String[] args) {
     7         DiscountStrategy tenPercent = new PercentOffDiscount(0.10);
     8         DiscountStrategy twentyPercent = new PercentOffDiscount(0.20);
     9
    10         double price = 50.0;
    11
    12         System.out.printf("Original price: $%.2f%n", price);
    13         System.out.printf("10%% off: $%.2f%n", tenPercent.apply(price));
    14         System.out.printf("20%% off: $%.2f%n", twentyPercent.apply(price));
    15     } // main
    16} // DiscountDemo
    

The overall process outlined above has real, tangible benefits. For example, the new class can:

  1. be reused (by utilizing its constructor);

  2. have instance variables that allow its objects to manage their state;

  3. include additional (helper) methods; and

  4. include documentation that differs from or adds on to the interface documentation in some way.

All of that sounds great, but what if you do not need one or more of those benefits? What if you don't need any of them? Suddenly, the five-step process described earlier for implementing the interface sounds tedious, especially in cases where the interface only has one abstract method.

11.2.4. Implementing a Functional Interface

If an interface in Java is considered a functional interface, then an alternative two-step process can be used to implement and utilize it using Java's lambda expression syntax:

  1. Instantiate: Use a lambda expression to create an object of an unnamed class that implements the interface by defining what the override behavior should be for the one abstract method in the interface (all in one place).

  2. Interact using the interface: When interacting with the object, use a variable with the interface as its type so that your code works with any object (of a class) that implements the interface and not just objects of the new class you created.

We will demonstrate this process with multiple examples over the next few sections of the chapter.

Test Yourself!

  • Is a functional interface allowed to have more than one abstract method?

  • Is Predicate a functional interface? Why or why not?

  • Is Comparator a functional interface? Why or why not?

  • Is Runnable a functional interface? Why or why not?

  • Is TemporalAccessor a functional interface? Why or why not?

Test Yourself Solutions (open after answering the questions above)
  • Yes, if the additional abstract methods are also found in the Object class.

  • Yes! While the interface contains several default and static methods, it has only one abstract method called test.

  • Yes! The interface has two abstract methods, compare and equals. However, the equals method is also found in Object. If in doubt, we recommend checking the API for the Object class.

  • Yes! Runnable has a single abstract method called run.

  • No, the interface has two abstract methods and neither are found in Object.