Last Updated on July 17, 2024
In Java, Functional Interfaces are a core concept introduced in Java 8 that enable a more functional programming style.
A functional interface is an interface that has exactly one abstract method. It can also have any number of default and static methods.
Functional interfaces serve as a target type for lambda expressions and method references, promoting a concise and readable way to define functionality.
They allow developers to write code that focuses on “what” needs to be done rather than “how” it’s done, often leading to cleaner and more maintainable code.
A functional interface is also called Single Abstract Method (SAM) interface as it only contains one abstract method and can contain any number of default or static methods.
A Java functional interface can be implemented by a Java Lambda Expression.
Lambda expressions, introduced in Java 8, offer a concise and anonymous way to define functionality.
Example:
@FunctionalInterface
public interface MathOperation { int operation(int a, int b); }
// Lambda expression assigned to a variable
MathOperation add = (a, b) -> a + b;
// Lambda expression passed as an argument
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.forEach(number -> System.out.println(number * 2));
Java mandates that lambda expressions can only be assigned to variables or passed as arguments if the target type is a functional interface. A functional interface, by definition, has a single abstract method.
These functional interfaces are defined in the java.util.function package.
Java Built-in Functional Interfaces
Java provides a collection of pre-defined functional interfaces for common tasks, saving you the time and effort of creating your own for every situation.
We’ll explore some of these handy interfaces in the following sections
Function
The java.util.function.Function interface is a fundamental building block in Java’s functional programming features.
It represents a function that takes one argument of type T and returns a value of type R.
The abstract method in Function is named apply(T t), where T is the type of the input argument and the return type is R.
public interface Function<T,R> {
public <R> apply(T parameter);
//Non-abstract methods
………
}
The Function interface offers a few additional methods beyond apply(T t). These methods come with pre-written implementations, so you don’t need to create your own versions unless you have a specific reason to modify them.
// Lambda expression example
Function<String, Integer> stringLength = s -> s.length();
int stringLengthResult = stringLength.apply("Hello"); // stringLengthResult will be 5
It is commonly used for mapping or transforming input to output.
Predicate
The Predicate
interface in Java, introduced in Java 8, is another fundamental building block for functional programming.
It represents a function that takes one argument of any type and returns a boolean value (true
or false
).
The abstract method in Predicate
is named test(T t)
, where T
is the type of the input argument and the return type is boolean
.
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
//Non-abstract methods
………
}
The Predicate
interface is frequently used in conjunction with lambda expressions and method references to filter elements within collections or streams.
It allows you to define conditions or criteria for selecting elements based on specific logic.
// Lambda expression example
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
.filter(number -> number % 2 == 0) // Uses Predicate with lambda expression
.forEach(System.out::println); // Prints even numbers
The Predicate
interface is a valuable tool for filtering elements based on boolean conditions.
Supplier
It represents a supplier of a value.
get()
: This abstract method has no arguments and returns a value of type T
. The specific type T
depends on the context in which you use the Supplier
interface.
@FunctionalInterface
public interface Supplier<T> {
T get();
//Non-abstract methods
………
}
It can be used to generate random values, read data from a source like a file or network, or provide default values.
// Generating a random number
Supplier<Double> randomDoubleSupplier = () -> Math.random();
double randomNumber = randomDoubleSupplier.get();
Consumer
It represents an operation that consumes a single argument but doesn’t return any value.
accept(T t)
: This abstract method takes one argument of type T
and performs some operation on it. The specific type T
depends on the context in which you use the Consumer
interface.
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
//Non-abstract methods
………
}
You can use Consumer
to perform actions on individual elements within collections or streams. This includes tasks like printing, logging, or modifying elements.
// Printing elements
List<String> names = Arrays.asList("John", "Mary", "Peter");
names.forEach(name -> System.out.println(name.toUpperCase())); // Uses Consumer with lambda expression
Functional programming generally avoids side effects, Consumer
is designed for scenarios where you need to perform actions with potential side effects, like modifying data or interacting with external resources.
UnaryOperator
The UnaryOperator
interface in Java, is a specialized functional interface designed to work with a single argument and return a value of the same type.
T apply(T t)
: This abstract method takes one argument of type T
and returns a value of type T
as well. The specific type T
depends on the context in which you use the UnaryOperator
interface.
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
T apply(T t);
//Non-abstract methods
………
}
You can use UnaryOperator
to perform operations like incrementing, converting to uppercase, or applying any logic that modifies a value and returns a new value of the same type.
// Incrementing numbers
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
.map(UnaryOperator.identity()) // Keeps elements unchanged (useful for chaining operations)
.map(number -> number + 1) // Uses UnaryOperator with lambda expression
.forEach(System.out::println); // Prints incremented numbers
BinaryOperator
The BinaryOperator
interface in Java, is a functional interface designed for performing operations on two operands and returning a result of the same type.
T apply(T t1, T t2)
: This abstract method takes two arguments of type T
and returns a value of type T
. The specific type T
depends on the context in which you use the BinaryOperator
interface.
The BinaryOperator
interface extends the BiFunction
interface. However, it enforces the constraint that all three types (input arguments and return type) must be the same.
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T, T, T> {
T apply(T t1, T t2);
}
BinaryOperator
is a preferred choice for stream reduction operations where you want to combine elements while maintaining the same type.
// Adding numbers
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, Integer::sum); // Uses BinaryOperator with method reference (reduction)
Conclusion
Functional interfaces have revolutionized the way developers approach tasks in Java. By leveraging these interfaces alongside lambda expressions, code becomes more concise, readable, and expressive.
Understanding and using functional interfaces effectively unlocks a whole new level of development in Java. They provide a powerful toolkit for writing clean, concise, and functional code, making your applications more maintainable and expressive.