Skip to content
Theory · Lambdas and the Stream API · lesson 1 of 12

Lambda expressions

What you'll learn

What you'll learn:

  • pass an action to a method as a lambda expression and read the w -> … notation;
  • use the Function and Predicate interfaces, method references and Comparator;
  • build a stream from filter, map and toList and explain why it does nothing without a terminal operation;
  • get a result out of an Optional without 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:

JAVA
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:

  1. words is a modifiable ArrayList of four words. You cannot remove elements from a List.of list, so the words are copied into an ArrayList.
  2. words.removeIf(w -> w.length() < 4): the removeIf method walks the list by itself, like a station along the belt. The card w -> w.length() < 4 tells it what to do with each word w: remove it if it has fewer than four letters.
  3. System.out.println(words) prints [zebra, horse]: the words cat and ox are gone.
  4. words.forEach(...) also walks the list and runs its own card for each word. That card prints the word in capital letters, so ZEBRA and HORSE come 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:

JAVA
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:

JAVA
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:

  1. interface Rule declares one method without a body, check: take a string and return a boolean. There are no other abstract methods, so the interface is functional.
  2. Rule isShort = w -> w.length() < 4; turns the lambda into an object of type Rule, and its body becomes the body of check.
  3. isShort.check("ox") runs the lambda's body for "ox" and prints true, and for "horse" it prints false.

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:

TEXT
error: local variables referenced from a lambda expression must be final or effectively final

Why 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:

JAVA
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 -> body is 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.