Java Lambda Expressions

A Java lambda expression is a short way to write a method-like block of code that can be passed around as a value. Lambdas are commonly used when an interface needs exactly one abstract method, such as with collection sorting, filtering, and callbacks.

Why lambdas are useful

Before lambdas, Java often used anonymous classes for small pieces of behavior. Lambdas make that same idea shorter and easier to read. They are especially useful with collections because you can tell Java what action to run for each item, how to compare two items, or how to test whether an item matches a rule.

A lambda does not stand alone by itself. It is assigned to, or passed where Java expects, a functional interface. A functional interface is an interface with one abstract method. Java can match the lambda’s parameters and return value to that method.

Basic syntax

The general shape is parameters -> expression. For more than one statement, use a block after the arrow. If the block must produce a value, use return inside the block.

If there is one parameter, parentheses are optional. If there are zero or multiple parameters, use parentheses. Java can usually infer the parameter types from the functional interface.

Example with functional interfaces

The java.util.function package contains common functional interfaces. In this example, Predicate<String> represents a test that returns true or false, and Consumer<String> represents an action that accepts a value and returns nothing.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;

public class Main {
    public static void main(String[] args) {
        List<String> tasks = new ArrayList<>(Arrays.asList("read", "study", "review"));

        Predicate<String> longTask = task -> task.length() >= 5;
        Consumer<String> printUpper = task -> System.out.println(task.toUpperCase());

        System.out.println("Tasks with 5+ letters:");
        for (String task : tasks) {
            if (longTask.test(task)) {
                System.out.println(task);
            }
        }

        tasks.forEach(printUpper);
    }
}

Output:

Tasks with 5+ letters:
study
review
READ
STUDY
REVIEW

How the example works

The lambda task -> task.length() >= 5 matches the single abstract method of Predicate<String>. That method is test, so the code calls longTask.test(task) inside the loop.

The lambda task -> System.out.println(task.toUpperCase()) matches Consumer<String>. A consumer performs an action, so it is a good match for printing each item. The forEach method accepts a consumer and runs it once for every element in the list.

Lambdas with multiple parameters

Some functional interfaces need more than one parameter. A common example is Comparator<T>, which compares two values for sorting.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>(Arrays.asList("Mia", "Alexander", "Bo"));

        names.sort((first, second) -> first.length() - second.length());

        System.out.println(names);
    }
}

Output:

[Bo, Mia, Alexander]

The lambda has two parameters, so it uses parentheses: (first, second). The expression after the arrow returns an integer. A negative value means the first name should come before the second, a positive value means it should come after, and zero means they are equal for this comparison.

Expression body vs block body

A lambda can use a single expression or a block. A single expression automatically becomes the return value when the functional interface expects one, as in name -> name.length(). A block body is useful when you need more than one statement, as in name -> { int length = name.length(); return length; }.

Variables inside lambdas

Lambdas can read local variables from the surrounding method only if those variables are final or effectively final. Effectively final means the variable is assigned once and not changed later. This rule prevents confusing behavior when lambdas run after the surrounding code has moved on.

When to use lambdas

  • Use a lambda for short behavior passed to a method.
  • Use a named method when the logic is long or reused in several places.
  • Use clear parameter names so the lambda remains readable.

Takeaway: a lambda expression is Java’s compact syntax for supplying behavior to a functional interface, and it is most useful when working with collections and small callbacks.