Key functional interfaces in the Java standard library, with lambda implementation examples.
Java 8 added a few important features to the Java language, accompanied by several additions to the standard library. The most important language addition was support for lambdas (sometimes called anonymous functions or closures). In order to implement this feature in a manner consistent with the historical aims and use of the language, it was decided that lambdas would be restricted to implementation of functional interfaces—interfaces with exactly one unimplemented method. Such interfaces had already existed in the standard library, but the distinction was formalized in Java 8, and several new functional interfaces were added to the standard library. This module is an overview of some of the most commonly used functional interfaces, with lambda-based implementation examples.
Note: Due to the lambda-centric focus of this module, Comparable
<T>
, Iterable
<T>
, Closeable
, and AutoCloseable
are not addressed, even though they are technically functional interfaces (since each has exactly one unimplemented method)—as well as being important interfaces, playing key roles in the JCL and in the Java language itself. The purposes of those interfaces essentially dictate that implementations perform operations on the states of instances of the implementing classes; thus, they are not well-suited to implementation via lambdas, which don’t define accessible state.
A functional interface (FI) is one that has exactly one abstract method—that is, a method declared in the interface (or a superinterface), but not implemented, and not matching any of the methods defined in the Object
class. (The last group are not considered abstract in this context, since every object inherits implementations of the methods declared in the Object
class.)
In Java, a lambda is a code construct treated by the compiler as an instance of an implementation of a functional interface—but with a more compact syntax than would be required for a class-based implementation.1
In a code context where an implementation of a given functional interface is expected, the Java compiler recognizes the lambda syntax:
([ paramDeclaration [, paramDeclaration [...] ] ]) -> body
A lambda is an object, of the type of the functional interface being implemented. It can be assigned to a suitable variable, passed as an argument in a method invocation, etc. For example, this statement defines a lambda that implements BinaryOperator
<Integer>
, and assigns it to a variable of the same type:
BinaryOperator<Integer> maxAbsolute = (a, b) -> (Math.abs(b) > Math.abs(a)) ? b : a;
paramDeclaration
Parameter declaration. This may include the parameter type or not; if any parameter declaration in the list includes the parameter type, then the types of all of the parameters in the list must be included. If parameter types are not included in the declaration, they will be inferred from the parameter types of the abstract method in the FI.
If (and only if) there is exactly one parameter declared, the surrounding parentheses are optional. (Some style guides—including the DDC Java+Android Style Guide—require the parentheses regardless of the number of parameters declared).
body
Code that implements the abstract method declared in the FI.
The body of a lambda may be written in almost exactly the same fashion as a method: zero or more statements enclosed in braces, referencing the parameter values to perform some operation, and (for a non-void
return type) returning a computed value.2 This type of lambda is called a statement lambda.
If the body of the lambda consists of a single, simple statement—which may include operators (including the ternary operator) and operands (including values returned from method invocations), but no flow control statements such as if
, for
, while
, do
-while
, switch
, try
—then it can be written as an expression lambda: a single statement, not enclosed in braces, and without a closing semicolon.
If a lambda …
… the lambda may be expressed as a method reference. In a method reference, the lambda parameter list (including the parentheses), the lambda operator itself, and the method invocation parameters (including the parentheses) are omitted entirely. Instead, just the method name, qualified by the method’s class (or the target object, if there is one), is used.
If the abstract method in the FI has a non-void
return type, then (of course) the lambda must return such a value. In a statement lambda, this is done via the usual return
statement. In an expression lambda, the value of the single expression making up the lambda body is automatically returned (in fact, it’s an error to use return
in an expression lambda). For a method reference, the referred-to method must have a return type that is assignment-compatible with the return type of the abstract method in the FI.
If the abstract method has a void
return type, then a statement lambda must not return a value; the value (if any) of an expression lambda will be ignored, as will the return value of a method reference.
Assume we have a collection of objects, declared as Collection<Object> data
, then we could use an implementation of Consumer
(Object)
in an invocation of data.forEach
, to print all of the objects in the collection. All 4 of the following code fragments would do this.
data.forEach(new Consumer<Object>() {
public void accept(Object obj) {
System.out.println(obj);
}
});
data.forEach((obj) -> {
System.out.println(obj);
});
data.forEach((obj) -> System.out.println(obj));
data.forEach(System.out::println);
Each of the pages following this introduction introduces a functional interface in the JCL, with a summary of its purpose and one or more lambda-based example code fragments.
A unit of processing that can be (but is not necessarily) assigned to run on a thread other than the current thread.
java.util.function.
Predicate
<T>
Applies a test to an object (usually in a Stream<T>
, Collection<T>
, Optional<T>
, etc.), returning a boolean
flag with the result of the test.
java.util.function.
Consumer
<T>
Performs some operation on an object (e.g. in a Stream
<T>
, Collection
<T>
, or Optional<T>
).
java.util.function.
Supplier
<T>
Acts as a source of values for a Stream
<T>
, an alternative value for an Optional<T>
, etc.
java.util.function.
Function
<T,R>
Maps or mutates an object—e.g. in a Stream
<T>
or Optional<T>
—returning the new or mutated object of type R
.
java.util.function.
BinaryOperator
<T>
Operates on 2 values of type T
, returning a single value of the same type.
Compares 2 values of type T
, returning an int
indicating how the 2 compare to each other for the purpose of ordering.
This module is by no means a complete reference on functional interfaces and lambdas; it is intended solely as an introduction to some of the key functional interfaces in the JCL, and to the basics of implementing them via lambdas.
As a next step, consider reading and doing the programming exercises in the lambdas section of “Lesson: Classes and Objects” in “Trail: Learning the Java Language” of the Oracle Java Tutorials.
There are dozens of functional interfaces in the java.util.function
package, and dozens more in other packages, in addition to the 7 introduced here. The definitive reference on classes and interfaces in the Java standard library is always the JCL specification. (Remember that an interface can be an FI without the @FunctionalInterface
annotation.)
Many event listeners in GUI frameworks and other event-driven libraries are defined as functional interfaces. Details on these can be found in the documentation for Swing, JavaFX, and Android. There are also trails in the Oracle Java Tutorials for creating a GUI with Swing and creating a JavaFX GUI.
In some other programming languages, especially dynamically typed languages such as Python or JavaScript, lambdas are not limited to implementing interfaces already known to the compiler. ↩
One important difference between a lambda and the corresponding method in a class-based implementation of an FI is the meaning of the keyword this
: In a class-based implementation, this
refers to the current instance of the class that implements the FI; in a lambda, this
refers not to the lambda, but to the current instance of the enclosing class. ↩