Lambda expressions
What you'll learn:
- pass an action to a method as a lambda expression and read the
w -> …notation; - use the
FunctionandPredicateinterfaces, method references andComparator; - build a stream from
filter,mapandtoListand explain why it does nothing without a terminal operation; - get a result out of an
Optionalwithout an exception.
Picture a conveyor belt in a factory. Boxes ride along the belt, and stations stand beside it. The worker at a station does the same thing to every box that reaches them.
What exactly to do is written on an instruction card: "take everything lighter than a kilogram off the belt" or "put a label on every box". The station itself does not change. Only the card it was handed changes.
So far, methods in Java have taken values: numbers, strings, objects. But how do you pass a method an action, "what to do with each element"? In the interfaces theme you would have to write a whole class for one line of code. A lambda expression hands over such a card in one line:
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> words =
new ArrayList<>(List.of("cat", "zebra", "ox", "horse"));
words.removeIf(w -> w.length() < 4);
System.out.println(words); // [zebra, horse]
words.forEach(w -> System.out.println(w.toUpperCase()));
}
}Let's go through the program step by step:
wordsis a modifiableArrayListof four words. You cannot remove elements from aList.oflist, so the words are copied into anArrayList.words.removeIf(w -> w.length() < 4): theremoveIfmethod walks the list by itself, like a station along the belt. The cardw -> w.length() < 4tells it what to do with each wordw: remove it if it has fewer than four letters.System.out.println(words)prints[zebra, horse]: the wordscatandoxare gone.words.forEach(...)also walks the list and runs its own card for each word. That card prints the word in capital letters, soZEBRAandHORSEcome out, each on its own line.
How a lambda is written
A lambda expression is a short way to write an action: the parameters go left of the arrow ->, the body goes right of it. The notation w -> w.length() < 4 reads: "take w and answer whether it has fewer than four letters".
You do not write the parameter's type. The compiler knows that words is a list of strings, so w is a String. You choose the parameter's name, as with a variable.
One parameter
With one parameter, no parentheses go around it: w -> w.length() < 4. A body of one expression needs no return: the lambda returns its value by itself.
Zero or several parameters
With no parameters or with several, the parentheses are required. The lambda () -> "hello" takes nothing and returns a string, while (a, b) -> a + b takes two values and returns their sum.
A body of several lines
When an action needs several lines, the body goes in braces, like a method body:
String letter = "z";
words.forEach(w -> {
if (w.contains(letter)) {
System.out.println("found " + w); // found zebra
}
});Inside the braces, each statement ends with a semicolon. The card for forEach returns nothing. A lambda that must return a value needs an explicit return in such a body.
Functional interfaces
So what type does the card itself have? Recall the interfaces theme: a variable of an interface type took an object of any class that implements the interface. A lambda works the same way: Java makes an object out of it, and that object implements an interface with one method. A functional interface is an interface with exactly one abstract method. Let's write our own:
public class Main {
public static void main(String[] args) {
Rule isShort = w -> w.length() < 4;
System.out.println(isShort.check("ox")); // true
System.out.println(isShort.check("horse")); // false
}
}
interface Rule {
boolean check(String w);
}Line by line:
interface Ruledeclares one method without a body,check: take a string and return aboolean. There are no other abstract methods, so the interface is functional.Rule isShort = w -> w.length() < 4;turns the lambda into an object of typeRule, and its body becomes the body ofcheck.isShort.check("ox")runs the lambda's body for"ox"and printstrue, and for"horse"it printsfalse.
From the single method, the compiler works out the lambda's parameters and what it returns. That is why a lambda can be written only where a functional interface is expected. removeIf and forEach expect such interfaces too, ready-made ones from the standard library. You can put @FunctionalInterface above your own interface, and the compiler will check that it has exactly one method without a body.
Interviewers like to ask what a functional interface is. The answer: an interface with exactly one abstract method, the one a lambda implements.
This is where the card picture is inaccurate. A paper card is read by a person, who may understand it in their own way. A lambda is an object, and the station calls its single method: there is nothing else it can do with it.
A lambda sees the method's variables
In the letter example, the lambda uses a variable declared outside it, in the main method. That is allowed, on one condition: the variable must not change after it gets its value. A variable that is never assigned again is called effectively final: it has no final keyword, but it behaves as if it were final. javac's messages call it effectively final as well.
Why such a rule? A lambda can be stored in a variable and run later, so Java copies the variable's value into the lambda. It is like a foreman writing the number onto the card at the moment the card goes to the station. If the variable changed later, the card and the variable would disagree. So Java forbids changing such a variable, both inside the lambda and outside it.
Write count++ inside a lambda for a local variable count, and javac answers:
error: local variables referenced from a lambda expression must be final or effectively finalWhy a variable used in a lambda must be effectively final is a common interview question.
Common mistake
The variable was not changed inside the lambda, but it was changed below:
int min = 4;
words.removeIf(w -> w.length() < min); // ✗
min = 5;The program does not compile, and javac points at the lambda line with the same message: local variables referenced from a lambda expression must be final or effectively final. The line below is to blame: the assignment min = 5 anywhere in the method makes min no longer effectively final. If you need another threshold, declare a new variable for it.
Key points
- A lambda expression
parameters -> bodyis a short way to write an action that a method runs by itself, for example for each element of a list. - A lambda becomes an object of a functional interface, an interface with exactly one abstract method.
- Parentheses around the parameters are needed when there is not exactly one; a body of several lines goes in braces and returns its value with
return. - A lambda may use a local variable only if it is effectively final: nothing is assigned to it after its first value.