Functional Interfaces: Introduction

Key functional interfaces in the Java standard library, with lambda implementation examples.

Overview

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.

Definition

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.)

Lambdas

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

Syntax

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;

Placeholders

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.

Statement lambda

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.

Expression 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.

Method reference

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.

Return value

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.

Syntax examples

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.

Anonymous class

data.forEach(new Consumer<Object>() {
  public void accept(Object obj) {
    System.out.println(obj);
  }
});

Statement lambda

data.forEach((obj) -> {
  System.out.println(obj);
});

Expression lambda

data.forEach((obj) -> System.out.println(obj));

Method reference lambda

data.forEach(System.out::println);

Examples

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.

Further reading

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.

  1. 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. 

  2. 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.