Java Functional Interfaces
A Java functional interface is an interface with exactly one abstract method. Because there is only one required method to implement, Java can use a lambda expression or method reference wherever that interface is expected.
Why Functional Interfaces Matter
Functional interfaces connect interfaces and lambdas. A lambda such as text -> text.length() does not have a type by itself. Java gives it a type by matching it to the single abstract method of a functional interface.
This idea is used throughout modern Java. Collection methods, sorting, filtering, event handlers, and stream operations all commonly accept functional interfaces.
The Single Abstract Method Rule
A functional interface must have one abstract method. It may also have default methods, static methods, and methods inherited from Object, but only one method can require implementation.
The @FunctionalInterface annotation is optional, but useful. It tells the compiler that the interface is meant to be functional. If someone later adds a second abstract method, the compiler reports an error.
Custom Functional Interface Example
This example defines a small functional interface named Formatter. The lambda assigned to it becomes the implementation of its single abstract method, format.
public class Main {
@FunctionalInterface
interface Formatter {
String format(String text);
}
public static void main(String[] args) {
Formatter shout = text -> text.toUpperCase() + "!";
Formatter bracket = text -> "[" + text + "]";
System.out.println(shout.format("hello"));
System.out.println(bracket.format("java"));
}
}
Output:
HELLO!
How the Example Works
The Formatter interface has one abstract method: format. Its method accepts a String and returns a String, so each lambda must accept one string-like parameter and produce a string result.
The variable shout stores one implementation, and bracket stores another. Both variables have the same interface type, but each lambda provides different behavior.
Common Built-In Functional Interfaces
Java provides many reusable functional interfaces in java.util.function. You should use these when their names match your purpose instead of creating a new interface for every small operation.
| Interface | Abstract method | Meaning |
|---|---|---|
Predicate<T> |
test |
Checks a value and returns true or false |
Function<T, R> |
apply |
Converts a value from one type to another |
Consumer<T> |
accept |
Uses a value and returns nothing |
Supplier<T> |
get |
Produces a value without receiving one |
Example With Built-In Interfaces
The next program uses four common functional interfaces. Notice how each lambda matches the parameter list and return type of the interface method.
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) {
Predicate<String> isLong = word -> word.length() > 5;
Function<String, Integer> length = word -> word.length();
Consumer<String> print = word -> System.out.println("Word: " + word);
Supplier<String> defaultWord = () -> "lambda";
String word = defaultWord.get();
print.accept(word);
System.out.println(length.apply(word));
System.out.println(isLong.test(word));
}
}
Output:
Word: lambda
6
true
Choosing an Interface
Look at what the operation needs to do. If it answers a yes-or-no question, use Predicate<T>. If it transforms one value into another, use Function<T, R>. If it performs an action such as printing or saving, use Consumer<T>. If it creates or supplies a value, use Supplier<T>.
Functional Interfaces and Generics
Most built-in functional interfaces are generic. For example, Predicate<String> tests strings, while Predicate<Integer> tests integers. Generic type arguments let the compiler check that each lambda receives and returns the correct types.
Common Mistakes
- Adding two abstract methods to an interface marked
@FunctionalInterface. - Writing a lambda whose parameters or return value do not match the interface method.
- Creating a custom interface when a clear built-in interface such as
PredicateorFunctionalready fits.
Takeaway: a functional interface gives a lambda a target type, making it possible to pass small pieces of behavior through normal Java interfaces.
