Java Functional Interfaces Cheat-Sheet
Table of Contents
- Java Functional Interfaces Cheat-Sheet
1.0 Overview:
Any interface
with a SAM (Single abstract method) is a functional interface.
@FunctionalInterface
public interface MyFunctionalInterface {
public void doSomething();
}
The @FunctionalInterface
annotation is preferred but not required. It can be helpful to add, however, as in addition to clarifying intent, it also allows the compiler to generate errors when the requirements for a functional interface are not met.
Methods signatures with default
and static
modifiers are not abstract, and as such a functional interface can have multiple default
or static
methods.
@FunctionalInterface
public interface MyFunctionalInterface {
public void doSomething();
public default void doSomethingElse() {
System.out.println("Something Else");
}
public static String getSomethingElse() {
return "SomethingElse";
}
}
Functional interfaces can be implemented with a concrete class.
public class SayHello implements MyFunctionalInterface
{
@Override
public void doSomething() {
System.out.println("Something");
}
}
They can also be implemented by a lambda expression.
MyFunctionalInterface saySomething = () -> {
System.out.println("Something");
}
There are a multitude of functional interfaces included in the core Java library’s java.util.function
package, but you can also create your own to suit any more specific needs you may have, as will be demonstrated further in the following sections.
2.0 Function T -> R
The simplest and most general functional Interface is the Function
interface itself which is parameterized by the types of its argument T
and a return value R
.
public interface Function<T, R> { … }
The single unimplemented method for Function
has the following signature.
R apply(T t);
Function
is commonly used for mapping objects of one type to another.
2.1 Primitive Functions
Since a primitive type can’t be a generic type argument, there are versions of the Function
interface for various combinations of argument and return types for commonly used primitives double
, int
, and long
.
Figure 2.1
int | long | double | generic | |
---|---|---|---|---|
int | X | IntToLongFunction | IntToDoubleFunction | IntFunction |
long | LongToIntFunction | X | LongToDoubleFunction | LongFunction |
double | DoubleToIntFunction | DoubleToLongFunction | X | DoubleFunction |
generic | ToIntFunction | ToLongFunction | ToDoubleFunction | X |
2.2 Two-Arity Functions (T, U) -> R
In some situations we may want to define a function with two arguments instead of one. Luckily, Java provides functional interfaces for such scenarios as a part of the JDK. The generic two-arity version of Function
is BiFunction
. It is parameterized by the types of each argument T
and U
separately, as well as the return type R
.
public interface BiFunction<T, U, R> { … }
The single unimplemented method for BiFunction
has the following signature.
R apply(T t, U u);
2.3 Primitive Two-Arity Functions
There are versions of the BiFunction
interface for commonly used primitive types double
, int
, and long
.
Figure 2.2
int | long | double | |
---|---|---|---|
generic | ToIntBiFunction | ToLongBiFunction | ToDoubleBiFunction |
Note that there are no two-arity specializations of BiFunction
that accept primitive argument types (only primitive return values). If such a need arises, we must create those functional interfaces ourselves.
/**
* Represents a function that accepts a long argument and produces a generic result.
*/
@FunctionalInterface
public interface LongBiFunction<R> {
R apply(long value);
}
2.4 Function Reference Examples
The following are useful reference examples either from the core Java library, or from industry standard libraries. They can give you a good starting point into understanding which scenarios Function
might be useful.
The Stream
interface, and it’s implementation in ReferencePipeline
use Function
for mapping a Stream
of one type (T
) to another (R
).
java/util/stream/ReferencePipeline.java 184
/**
* Returns a stream consisting of the results of applying the given
* function to the elements of this stream.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* @param <R> The element type of the new stream
* @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* function to apply to each element
* @return the new stream
*/
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
/// ...
@Override
@SuppressWarnings("unchecked")
public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {
Objects.requireNonNull(mapper);
return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
@Override
Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
return new Sink.ChainedReference<P_OUT, R>(sink) {
@Override
public void accept(P_OUT u) {
downstream.accept(mapper.apply(u));
}
};
}
};
}
The Arrays
class has a static
method which accepts a Function
to set all of the elements of an array at once. The Integer
indices for each element are the inputs to the Function
.
java/util/Arrays.java 4695
/**
* Set all elements of the specified array, using the provided
* generator function to compute each element.
*
* <p>If the generator function throws an exception, it is relayed to
* the caller and the array is left in an indeterminate state.
*
* @param <T> type of elements of the array
* @param array array to be initialized
* @param generator a function accepting an index and producing the desired
* value for that position
* @throws NullPointerException if the generator is null
* @since 1.8
*/
public static <T> void setAll(T[] array, IntFunction<? extends T> generator) {
Objects.requireNonNull(generator);
for (int i = 0; i < array.length; i++)
array[i] = generator.apply(i);
}
3.0 Supplier () -> T
The Supplier interface is a specialization of Function
which takes no arguments (nullary function) but still returns a value. It is parameterized only by the return type T
.
public interface Supplier<T> { … }
The single unimplemented method for Supplier
has the following signature.
T get();
By far the most common use case for Supplier
is lazy value initialization/generation. This can be useful if we have an object that is expensive to create.
Supplier<myObject> objectSupplier = () -> new myObject());
3.1 Primitive Suppliers
Similar to Function
, there are versions of the Supplier
interface for commonly used primitive types double
, int
, and long
. Breaking from the established pattern, Supplier
has one additional primitive specialization for the boolean
data type.
Figure 3.1
int | long | double | boolean | |
---|---|---|---|---|
void | IntSupplier | LongSupplier | DoubleSupplier | BooleanSupplier |
3.2 Supplier Reference Examples
The following are useful reference examples either from the core Java library, or from industry standard libraries. They can give you a good starting point into understanding which scenarios Supplier
might be useful.
In Java’s logging API, Supplier
is used for lazy value initialization. There is no need to construct the String
log message if it might not end up being logged.
java/util/logging/Logger.java 805
/**
* Log a message, which is only to be constructed if the logging level
* is such that the message will actually be logged.
* <p>
* If the logger is currently enabled for the given message
* level then the message is constructed by invoking the provided
* supplier function and forwarded to all the registered output
* Handler objects.
* <p>
* @param level One of the message level identifiers, e.g., SEVERE
* @param msgSupplier A function, which when called, produces the
* desired log message
* @since 1.8
*/
public void log(Level level, Supplier<String> msgSupplier) {
if (!isLoggable(level)) {
return;
}
LogRecord lr = new LogRecord(level, msgSupplier.get());
doLog(lr);
}
Starting with JUnit 5, The Assertions
class allows you to provide a Supplier<String>
instead of a String
literal error message. The actual String
message isn’t constructed until necessary, and sometimes not even at all (lazy initialization).
org/junit/jupiter/api/Assertions 950
/**
* <em>Assert</em> that {@code expected} and {@code actual} are equal.
* <p>Equality imposed by this method is consistent with {@link Double#equals(Object)} and
* {@link Double#compare(double, double)}.
* <p>If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}.
*/
static void assertEquals(double expected, double actual, Supplier<String> messageSupplier) {
if (!doublesAreEqual(expected, actual)) {
failNotEqual(expected, actual, messageSupplier);
}
}
// ...ommitted code
static void failNotEqual(Object expected, Object actual, Supplier<String> messageSupplier) {
fail(format(expected, actual, nullSafeGet(messageSupplier)), expected, actual);
}
// ...ommitted code
static String nullSafeGet(Supplier<String> messageSupplier) {
return (messageSupplier != null ? messageSupplier.get() : null);
}
With a CompleteableFuture
you can asynchronously retrieve a value from a Supplier
.
java/util/concurrent/CompleteableFuture 1813
/**
* Returns a new CompletableFuture that is asynchronously completed
* by a task running in the {@link ForkJoinPool#commonPool()} with
* the value obtained by calling the given Supplier.
*
* @param supplier a function returning the value to be used
* to complete the returned CompletableFuture
* @param <U> the function's return type
* @return the new CompletableFuture
*/
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return asyncSupplyStage(asyncPool, supplier);
}
4.0 Consumer T -> void
The Consumer
interface accepts a generified argument and returns nothing(void
). It can be thought of as the opposite of Supplier
. It is parameterized only by the type of it’s argument T
.
public interface Consumer<T> { … }
The single unimplemented method for Consumer
has the following signature.
void accept(T t);
There are a multitude of functional interfaces that are useful when you want a value, but Consumer
is most useful when you want an action. In fact, as you’ll see in the reference examples, Consumer
variables almost always have “action” as a part of their name.
Consumer<String> stringPrinter = s -> System.out.println(s);
4.1 Primitive Consumers
Once again there are versions of the Consumer
interface for commonly used primitive types double
, int
, and long
.
Figure 4.1
void | |
---|---|
int | IntConsumer |
long | LongConsumer |
double | DoubleConsumer |
4.2 Two-Arity Consumers (T, U) -> void
The generic two-arity version of Consumer
is BiConsumer
. The argument types do not need to match since BiConsumer
parameterized by the types of each argument T
and U
separately.
public interface BiConsumer<T, U> { … }
BiConsumer
has the following SAM signature:
void accept(T t, U u);
4.3 Primitive Two-Arity Consumers
Again there are versions of the BiConsumer
interface for the most used primitive types double
, int
, and long
. Note that only one of the arguments is generified, while the other is of the primitive type specified.
Figure 4.2
void | |
---|---|
int | ObjIntConsumer |
long | ObjLongConsumer |
double | ObjDoubleConsumer |
4.4 Consumer Reference Examples
The following are useful reference examples either from the core Java library, or from industry standard libraries. They can give you a good starting point into understanding which scenarios Consumer
might be useful.
Iterable
(and thus out favorite collection classes) uses Consumer
for all of the forEach()
type methods, when an action needs to be performed on every element.
java/lang/Iterable.java 72
/**
* Performs the given action for each element of the {@code Iterable}
* until all elements have been processed or the action throws an
* exception. Unless otherwise specified by the implementing class,
* actions are performed in the order of iteration (if an iteration order
* is specified). Exceptions thrown by the action are relayed to the
* caller.
*
* @implSpec
* <p>The default implementation behaves as if:
* <pre>{@code
* for (T t : this)
* action.accept(t);
* }</pre>
*
* @param action The action to be performed for each element
* @throws NullPointerException if the specified action is null
* @since 1.8
*/
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
Map
utilizes a BiConsumer
in its’ forEach()
method, in order to accept both the key and value to perform an action on.
java/util/Map.java 72
/**
* Performs the given action for each entry in this map until all entries
* have been processed or the action throws an exception. Unless
* otherwise specified by the implementing class, actions are performed in
* the order of entry set iteration (if an iteration order is specified.)
* Exceptions thrown by the action are relayed to the caller.
*
* @implSpec
* The default implementation is equivalent to, for this {@code map}:
* <pre> {@code
* for (Map.Entry<K, V> entry : map.entrySet())
* action.accept(entry.getKey(), entry.getValue());
* }</pre>
*
* The default implementation makes no guarantees about synchronization
* or atomicity properties of this method. Any implementation providing
* atomicity guarantees must override this method and document its
* concurrency properties.
*
* @param action The action to be performed for each entry
* @throws NullPointerException if the specified action is null
* @throws ConcurrentModificationException if an entry is found to be
* removed during iteration
* @since 1.8
*/
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
5.0 Predicate T -> boolean
The Predicate
functional interface is a specialization of Function
that receives a generified value and returns a boolean
. It is parameterized by its argument type T
.
public interface Predicate<T> { … }
Predicate
has the following unimplemented method.
boolean test(T t);
A Predicate
can be thought of as evaluating a binary relation, where the relation definition and one of the arguments are both established as a part of the Predicate declaration.
“A binary relation encodes the common concept of relation: an element x is related to an element y, if and only if the pair (x, y) belongs to the set of ordered pairs that defines the binary relation.”
-Wikipedia
Some common examples of binary relations are Greater Than, Less Than, Equals, Divides Evenly, etc.
Predicate<Integer> greatherThanZero = x -> x > 0;
In this example the relation is “Greater Than”, and y
is 0. When we supply an integer x
to the Predicate
, it tests whether the ordered pair (x, 0) satisfies the relation, returning true if so, and false if not.
Figure 5.1
x | y | relation | evaluation |
---|---|---|---|
-2 | 0 | x > y | false |
-1 | 0 | x > y | false |
0 | 0 | x > y | false |
1 | 0 | x > y | true |
2 | 0 | x > y | true |
5.1 Primitive Predicates
Once again there are versions of the Predicate
interface for commonly used primitive types double
, int
, and long
.
Figure 5.2
boolean | |
---|---|
int | IntPredicate |
long | LongPredicate |
double | DoublePredicate |
5.2 Two-Arity Predicates (T, U) -> boolean
The two-arity version of Predicate
is BiPredicate
. It is parameterized by the types of each argument T
and U
separately.
public interface BiPredicate<T, U> { … }
It has the following unimplemented method signature.
boolean test(T t, U u);
5.3 Primitive Two-Arity Predicates
There are no included functional interface for primitive specializations of BiPredicate
.
5.4 Predicate Reference Examples
The Stream
interface, and it’s implementation in ReferencePipeline
use Predicate
for filtering. Filtering a Collection
is probably the most common use of Predicate
.
java/util/stream/ReferencePipeline.java 160
/**
* Returns a stream consisting of the elements of this stream that match
* the given predicate.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* @param predicate a <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* predicate to apply to each element to determine if it
* should be included
* @return the new stream
*/
@Override
public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) {
Objects.requireNonNull(predicate);
return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE,
StreamOpFlag.NOT_SIZED) {
@Override
Sink<P_OUT> opWrapSink(int flags, Sink<P_OUT> sink) {
return new Sink.ChainedReference<P_OUT, P_OUT>(sink) {
@Override
public void begin(long size) {
downstream.begin(-1);
}
@Override
public void accept(P_OUT u) {
if (predicate.test(u))
downstream.accept(u);
}
};
}
};
}
The following are useful reference examples either from the core Java library, or from industry standard libraries. They can give you a good starting point into understanding which scenarios Predicate
might be useful.
The Pattern class allows you convert a Pattern
into a Predicate
which can be used for String
matching.
java/util/regex/Pattern.java 5757
/**
* Creates a predicate which can be used to match a string.
*
* @return The predicate which can be used for matching on a string
* @since 1.8
*/
public Predicate<String> asPredicate() {
return s -> matcher(s).find();
}
6.0 Operator
Operator interfaces are special cases of Function that receive and return the same value type.
6.1 UnaryOperator T -> T
The UnaryOperator
interface receives a single argument and returns a value of the same type. As such, it is the only functional interfaces to directly extend Function
. (There are semantic reasons why Consumer
and Supplier
do not extend Function
)
public interface UnaryOperator<T> extends Function<T, T>
Because it extends Function
, UnaryOperator
inherits the following unimplemented method signature.
T apply(T t);
To demonstrate the fact that UnaryOperator
is simply a QOL functional interface, does not provide additional functionality on top of Function
, take a look at the two examples below. Anywhere UnaryOperator
is used, Function
would work just as well, albeit slightly more verbose declaratively.
UnaryOperator<Person> unaryOperator =
(person) -> { person.name = "New Name"; return person; };
The only difference when using Function
is that the argument and return types must be specified explicitly.
Function<Person, Person> function =
(person) -> { person.name = "New Name"; return person; };
6.2 Primitive UnaryOperator
Again, since a primitive type can’t be a generic type argument, there are versions of the UnaryOperator
interface for commonly used primitive types double
, int
, and long
.
Figure 6.1
int | long | double |
---|---|---|
IntUnaryOperator | LongUnaryOperator | DoubleUnaryOperator |
6.3 BinaryOperator (T, T) -> T
The BinaryOperator
interface receives two arguments and returns a value of the same type. Similar to UnaryOperator
is an extension of another functional interface, in this case BiFunction
.
public interface BinaryOperator<T> extends BiFunction<T,T,T> { ... }
Because it extends BiFunction
, BinaryOperator
inherits the following unimplemented method signature.
T apply(T t, T t2);
6.4 Primitive BinaryOperator
Again, since a primitive type can’t be a generic type argument, there are versions of the BinaryOperator
interface for commonly used primitive types double
, int
, and long
.
Figure 6.2
int | long | double |
---|---|---|
IntBinaryOperator | LongBinaryOperator | DoubleBinaryOperator |
6.5 Operator Reference Examples
The List
interface, and its various implementations use UnaryOperator
for replacing each element in the List
with an element of the same type, effectively transforming the List
.
java/util/List.java 160
/**
* Replaces each element of this list with the result of applying the
* operator to that element. Errors or runtime exceptions thrown by
* the operator are relayed to the caller.
*
* @implSpec
* The default implementation is equivalent to, for this {@code list}:
* <pre>{@code
* final ListIterator<E> li = list.listIterator();
* while (li.hasNext()) {
* li.set(operator.apply(li.next()));
* }
* }</pre>
*
* If the list's list-iterator does not support the {@code set} operation
* then an {@code UnsupportedOperationException} will be thrown when
* replacing the first element.
*
* @param operator the operator to apply to each element
* @throws UnsupportedOperationException if this list is unmodifiable.
* Implementations may throw this exception if an element
* cannot be replaced or if, in general, modification is not
* supported
* @throws NullPointerException if the specified operator is null or
* if the operator result is a null value and this list does
* not permit null elements
* (<a href="Collection.html#optional-restrictions">optional</a>)
* @since 1.8
*/
default void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final ListIterator<E> li = this.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
}
The Stream
interface has a static method for generating an infinite Stream
by iteratively applying a UnaryOperator
to the result of the previous application.
java/util/stream/Stream.java 1020
/**
* Returns an infinite sequential ordered {@code Stream} produced by iterative
* application of a function {@code f} to an initial element {@code seed},
* producing a {@code Stream} consisting of {@code seed}, {@code f(seed)},
* {@code f(f(seed))}, etc.
*
* <p>The first element (position {@code 0}) in the {@code Stream} will be
* the provided {@code seed}. For {@code n > 0}, the element at position
* {@code n}, will be the result of applying the function {@code f} to the
* element at position {@code n - 1}.
*
* @param <T> the type of stream elements
* @param seed the initial element
* @param f a function to be applied to to the previous element to produce
* a new element
* @return a new sequential {@code Stream}
*/
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
Objects.requireNonNull(f);
final Iterator<T> iterator = new Iterator<T>() {
@SuppressWarnings("unchecked")
T t = (T) Streams.NONE;
@Override
public boolean hasNext() {
return true;
}
@Override
public T next() {
return t = (t == Streams.NONE) ? seed : f.apply(t);
}
};
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
iterator,
Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
}
7.0 Bonus Goodies
7.1 1-arity JDK Provided Functional Interface Table
Below is a full matrix of JDK supplied 1-arity functional interfaces by param and return type.
Figure 7.1
double | int | long | boolean | float | char | byte | generic | void | |
---|---|---|---|---|---|---|---|---|---|
double | DoubleUnaryOperator | DoubleToIntFunction | DoubleToLongFunction | DoublePredicate | X | X | X | DoubleFunction | DoubleConsumer |
int | IntToDoubleFunction | IntUnaryOperator | IntToLongFunction | IntPredicate | X | X | X | IntFunction | IntConsumer |
long | LongToDoubleFunction | LongToIntFunction | LongUnaryOperator | LongPredicate | X | X | X | LongFunction | LongConsumer |
boolean | X | X | X | X | X | X | X | X | X |
float | X | X | X | X | X | X | X | X | X |
char | X | X | X | X | X | X | X | X | X |
byte | X | X | X | X | X | X | X | X | X |
generic | ToDoubleFunction | ToIntFunction | ToLongFunction | Predicate | X | X | X | Function | Consumer |
void | DoubleSupplier | IntSupplier | LongSupplier | BooleanSupplier | X | X | X | Supplier | Runnable |
7.2 Legacy Functional Interfaces
As of Java 8, certain legacy interfaces that fit the requirement for a functional interface have had the @FunctionalInterface
annotation added, and can be implemented by a lambda expression. The following are just a select few examples.
Runnable: void -> T
Thread t1 = new Thread(() -> System.out.println("Running Task"));
/// above lambda can also be replaced with method reference...
Thread t1 = new Thread(() -> System.out.println("Running Task"));
Comparator: (T, T) -> int
Comparator<String> stringLengthComparator = (s1, s2) -> s1.length() - s2.length();
/// above lambda can also be replaced with method reference...
Comparator<String> stringLengthComparator = Comparator.comparingInt(String::length);
Callable: void -> void
Integer result = Executors.newSingleThreadExecutor().submit(() -> doExpensiveCalculation()).get();