Skip to main content

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 parameterized
  • ToIntFunction, ToLongFunction, ToDoubleFunction : return type is of specified type, arguments are parameterized
  • DoubleToIntFunction, 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 Supplier functional interface is yet another Function specialization 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 Consumer accepts 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 Predicate functional interface is a specialization of a Function that 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

Operator interfaces 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.

References