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:
Sign the contract: Create a class in its own
.javafile and include the appropriateimplementsclause in the class declaration.Code Example
public class PercentOffDiscount implements DiscountStrategy { // ... } // PercentOffDiscount
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
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
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);
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
DiscountStrategycan 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:
be reused (by utilizing its constructor);
have instance variables that allow its objects to manage their state;
include additional (helper) methods; and
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:
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).
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
Objectclass.Yes! While the interface contains several
defaultandstaticmethods, it has only oneabstractmethod calledtest.Yes! The interface has two abstract methods,
compareandequals. However, theequalsmethod is also found inObject. If in doubt, we recommend checking the API for theObjectclass.Yes!
Runnablehas a single abstract method calledrun.No, the interface has two abstract methods and neither are found in
Object.