3.4 Lambda Expressions
A “lambda expression” is a block of code that you can pass around so it can be executed later, once or multiple times. In the preceding sections, you have seen many situations where it is useful to specify such a block of code:
- To pass a comparison method to Arrays.sort
- To run a task in a separate thread
- To specify an action that should happen when a button is clicked
However, Java is an object-oriented language where (just about) everything is an object. There are no function types in Java. Instead, functions are expressed as objects, instances of classes that implement a particular interface. Lambda expressions give you a convenient syntax for creating such instances.
3.4.1 The Syntax of Lambda Expressions
Consider again the sorting example from Section 3.3.2, “The Comparator Interface,” on p. 104. We pass code that checks whether one string is shorter than another. We compute
first.length() - second.length()
What are first and second? They are both strings. Java is a strongly typed language, and we must specify that as well:
(String first, String second) -> first.length() - second.length()
You have just seen your first lambda expression. Such an expression is simply a block of code, together with the specification of any variables that must be passed to the code.
Why the name? Many years ago, before there were any computers, the logician Alonzo Church wanted to formalize what it means for a mathematical function to be effectively computable. (Curiously, there are functions that are known to exist, but nobody knows how to compute their values.) He used the Greek letter lambda (λ) to mark parameters, somewhat like
λfirst. λsecond. first.length() - second.length()
If the body of a lambda expression carries out a computation that doesn’t fit in a single expression, write it exactly like you would have written a method: enclosed in {} and with explicit return statements. For example,
(String first, String second) -> { int difference = first.length() < second.length(); if (difference < 0) return -1; else if (difference > 0) return 1; else return 0; }
If a lambda expression has no parameters, supply empty parentheses, just as with a parameterless method:
Runnable task = () -> { for (int i = 0; i < 1000; i++) doWork(); }
If the parameter types of a lambda expression can be inferred, you can omit them. For example,
Comparator<String> comp = (first, second) -> first.length() - second.length(); // Same as (String first, String second)
Here, the compiler can deduce that first and second must be strings because the lambda expression is assigned to a string comparator. (We will have a closer look at this assignment in the next section.)
If a method has a single parameter with inferred type, you can even omit the parentheses:
EventHandler<ActionEvent> listener = event -> System.out.println("Oh noes!"); // Instead of (event) -> or (ActionEvent event) ->
You never specify the result type of a lambda expression. However, the compiler infers it from the body and checks that it matches the expected type. For example, the expression
(String first, String second) -> first.length() - second.length()
can be used in a context where a result of type int is expected (or a compatible type such as Integer, long, or double).
3.4.2 Functional Interfaces
As you already saw, there are many interfaces in Java that express actions, such as Runnable or Comparator. Lambda expressions are compatible with these interfaces.
You can supply a lambda expression whenever an object of an interface with a single abstract method is expected. Such an interface is called a functional interface.
To demonstrate the conversion to a functional interface, consider the Arrays.sort method. Its second parameter requires an instance of Comparator, an interface with a single method. Simply supply a lambda:
Arrays.sort(words, (first, second) -> first.length() - second.length());
Behind the scenes, the second parameter variable of the Arrays.sort method receives an object of some class that implements Comparator<String>. Invoking the compare method on that object executes the body of the lambda expression. The management of these objects and classes is completely implementation-dependent and highly optimized.
In most programming languages that support function literals, you can declare function types such as (String, String) -> int, declare variables of those types, put functions into those variables, and invoke them. In Java, there is only one thing you can do with a lambda expression: put it in a variable whose type is a functional interface, so that it is converted to an instance of that interface.
The standard library provides a large number of functional interfaces (see Section 3.6.2, “Choosing a Functional Interface,” on p. 113). One of them is
public interface Predicate<T> { boolean test(T t); // Additional default and static methods }
The ArrayList class has a removeIf method whose parameter is a Predicate. It is specifically designed to pass a lambda expression. For example, the following statement removes all null values from an array list:
list.removeIf(e -> e == null);