6. Lambdas and Funtional Interfaces
1. Lambda Expressions
A lambda is an anonymous function that we can handle as a first-class language citizen. For instance, we can pass it to or return it from a method.
2. Functional Interfaces
Any interface with a single abstract method is a functional interface, and its implementation may be treated as lambda expressions.
💡 It’s recommended that all functional interfaces have an informative @FunctionalInterface annotation.
This clearly communicates the purpose of the interface, and also allows a compiler to generate an error if the annotated interface does not satisfy the conditions.
⚠️ An interface with only one method is functional but can still have default methods.
3. Functions
The most simple and general case of a lambda is a functional interface with a method that receives one value and returns another.
This function of a single argument is represented by the Function interface, which is parameterized by the types of its argument and a return value.
/**
* Represents a function that accepts one argument and produces a result.
*
* This is a functional interface whose functional method is apply(Object).
*
* @param <T> the type of the input to the function
* @param <R> the type of the result of the function
*/
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
}
Primitive Function Specializations
⚠️ Since a primitive type can’t be a generic type argument, there are versions of the Function interface for the most used primitive types double, int, long, and their combinations in argument and return types:
IntFunction, LongFunction, DoubleFunction: arguments are of specified type, return type is parameterizedToIntFunction, ToLongFunction, ToDoubleFunction: return type is of specified type, arguments are parameterizedDoubleToIntFunction, DoubleToLongFunction, IntToDoubleFunction, IntToLongFunction, LongToIntFunction, LongToDoubleFunction: having both argument and return type defined as primitive types, as specified by their names
Two-Arity Function Specializations
💡 To define lambdas with two arguments, we have to use additional interfaces that contain “Bi” keyword in their names: BiFunction, ToDoubleBiFunction, ToIntBiFunction, and ToLongBiFunction.
4. Suppliers
The
Supplierfunctional interface is yet anotherFunctionspecialization that does not take any arguments. We typically use it for lazy generation of values. There is no requirement that a new or distinct result be returned each time the supplier is invoked.
/**
* Represents a supplier of results.
*
* This is a functional interface whose functional method is get().
*
* @param <T> the type of results supplied by this supplier
*/
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
Example :
static Supplier<TodoNotFoundException> getTodoNotFoundExceptionSupplier(UUID id) {
return () -> new TodoNotFoundException(id);
}
💡 Optional.orElseThrow method implements the Supplier functional interface.
5. Consumers
The
Consumeraccepts a generified argument and returns nothing. It is a function that is representing side effects.
/**
* Represents an operation that accepts a single input argument and returns no
* result. Unlike most other functional interfaces, Consumer is expected
* to operate via side-effects.
*
* <p>This is a functional interface whose functional method is {accept(Object).
*
* @param <T> the type of the input to the operation
*/
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}
Example :
Consumer<Todo> getTodoConsumer() {
return (todo) -> todoRepository.remove(todo);
}
💡 List.forEach method implements the Consumer functional interface.
6. Predicates
In mathematical logic, a predicate is a function that receives a value and returns a boolean value. A typical use case of the Predicate lambda is to filter a collection of values.
The
Predicatefunctional interface is a specialization of aFunctionthat receives a generified value and returns a boolean.
/**
* Represents a predicate (boolean-valued function) of one argument.
*
* This is a functional interface whose functional method is test(Object).
*
* @param <T> the type of the input to the predicate
*/
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return true if the input argument matches the predicate, otherwise false.
*/
boolean test(T t);
}
Example :
Predicate<Todo> getTodoPredicate(Todo todoToUpdate) {
return todo -> !todo.equals(todoToUpdate);
}
💡 filter or anyMatch methods implements the Predicate functional interface.
7. Operators
Operatorinterfaces are special cases of a function that receive and return the same value type.
/**
* Represents an operation on a single operand that produces a result of the
* same type as its operand. This is a specialization of Function for
* the case where the operand and result are of the same type.
*
* This is a functional interface whose functional method is apply(Object).
*
* @param <T> the type of the operand and result of the operator
*/
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
/**
* Returns a unary operator that always returns its input argument.
*
* @param <T> the type of the input and output of the operator
* @return a unary operator that always returns its input argument
*/
static <T> UnaryOperator<T> identity() {
return t -> t;
}
}
Example:
static UnaryOperator<String> getStringUnaryOperator() {
return name -> name.toUpperCase();
}
💡 List.replaceAll method implements the UnaryOperator functional interface.