CodeGym /Courses /JAVA 25 SELF /Introduction to lambda expressions

Introduction to lambda expressions

JAVA 25 SELF
Level 21 , Lesson 0
Available

1. Introduction

In short: a lambda expression is a way to quickly create an implementation of a functional interface on the fly, without declaring a separate class or an anonymous class. It’s like a tiny method without a name that you can pass as an argument or store in a variable.

For a long time, Java was a “classic” OOP language. If you wanted to pass a small piece of behavior, for example, what to do when a button is clicked, you had to write anonymous classes:

button.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick() {
        System.out.println("Button clicked!");
    }
});

With Java 8 came lambda magic:

button.setOnClickListener(() -> System.out.println("Button clicked!"));

Where () -> System.out.println("Button clicked!") is the lambda expression.

Formally: a lambda expression is a compact way to write the implementation of the single abstract method of a functional interface.

Why is this important?

  • To pass behavior as a value (for example, what to do with each element of a list).
  • To write compact and readable code.
  • To use modern Java APIs: the Stream API, event handling, asynchronous tasks, and much more.

2. Lambda expression syntax

General form

(parameters) -> expression
// or
(parameters) -> { code block }

Examples for different interfaces

1. No parameters (e.g., Runnable):

Runnable r = () -> System.out.println("Hello, lambda!");
r.run();

2. One parameter (e.g., Consumer):

Consumer<String> printer = s -> System.out.println("You passed: " + s);
printer.accept("Java");

If there’s only one parameter and its type can be inferred, you can omit the parentheses.
The type String is the type of the variable (s) that we pass there.

3. Multiple parameters (e.g., Comparator):

Comparator<Integer> cmp = (a, b) -> a - b;
System.out.println(cmp.compare(10, 5)); // 5

The type Integer is the type of the variables (a, b) that we pass there.

4. Multi-line body (you need braces and return if there’s an explicit return):

Function<Integer, Integer> square = x -> {
    int result = x * x;
    return result;
};
System.out.println(square.apply(6)); // 36

The first Integer is the function’s return type; the second Integer is the parameter type.

Shorthand and conciseness

  • If the body is a single expression, you can omit the braces and return.
  • If there are no parameters, write empty parentheses: () -> ...
  • If there’s one parameter, you can omit the parentheses: x -> ...
  • If there’s more than one parameter, you need parentheses: (a, b) -> ...

Summary table

Let’s see how the same idea is written with a lambda vs. with an anonymous class:

Interface Lambda expression (example) Equivalent anonymous class
Runnable
() -> System.out.println("Hi")
new Runnable() { public void run() { System.out.println("Hi"); } }
Consumer<String>
s -> System.out.println(s)
new Consumer<String>() { public void accept(String s) { System.out.println(s); } }
Comparator<Integer>
(a, b) -> a - b
new Comparator<Integer>() { public int compare(Integer a, Integer b) { return a - b; } }

Example with a custom interface

Suppose we have a functional interface:

@FunctionalInterface
interface Operation {
    int apply(int a, int b);
}

Previously, you’d implement it like this:

Operation sum = new Operation() {
    @Override
    public void apply(int a, int b) {
        return a + b;
    }
};

Now — the modern way:

Operation sum = (a, b) -> a + b;
System.out.println(sum.apply(3, 5)); // 8

Examples for standard interfaces

Runnable:

Runnable hello = () -> System.out.println("Hello from thread!");
new Thread(hello).start();

Comparator:

List<String> list = Arrays.asList("apple", "banana", "kiwi");
list.sort((a, b) -> a.length() - b.length());
System.out.println(list);

Function:

Function<String, Integer> parse = s -> Integer.parseInt(s);
System.out.println(parse.apply("123")); // 123

3. Scope and variable capture

Variables from the outer context (effectively final)

Lambda expressions can use variables from the surrounding method. But there’s a rule: such variables must be effectively final — that is, either explicitly declared final, or simply not changed after initialization.

Example:

String prefix = "Result: ";
Function<Integer, String> f = x -> prefix + (x * 2);
// prefix is “frozen” here — you can’t change it after this
System.out.println(f.apply(5)); // Result: 10

If you try to change prefix after it’s used in a lambda, the compiler will throw an error.

Why?
A lambda expression can be invoked after the method where the variable was declared has returned. To avoid “magical” bugs, Java allows only immutable variables to be used.

Difference from anonymous classes

Anonymous classes follow the same rule: variables from the outer method must be final/effectively final. But there are scoping nuances: inside an anonymous class, this refers to the anonymous class itself, while in a lambda it refers to the outer class.

public class Demo {
    public void test() {
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println(this); // Will print: Demo$1 (anonymous class)
            }
        };

        Runnable r2 = () -> System.out.println(this); // Will print: Demo (outer class)

        r1.run();
        r2.run();
    }
}

Lambda and class fields

A lambda expression can access fields of the outer class without restrictions:

public class Counter {
    private int base = 10;

    public void printSum(int x) {
        Function<Integer, Integer> sum = y -> base + y + x;
        System.out.println(sum.apply(5));
    }
}

Here, base can be changed (it’s a class field).
x must be effectively final.

4. Practice: write a few lambda expressions

Example: filtering a list of numbers

List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6);

nums.stream()
    .filter(n -> n % 2 == 0)
    .forEach(n -> System.out.println("Even: " + n));

You’ll learn more about the Stream API at level 30 :P

Example: a string transformation function

Function<String, String> capitalize = s -> s.toUpperCase();
System.out.println(capitalize.apply("java")); // JAVA

Example: your own functional interface

@FunctionalInterface
interface StringTransformer {
    String transform(String s);
}

StringTransformer exclaim = s -> s + "!";
System.out.println(exclaim.transform("Hello")); // Hello!

Example: using variables from the outer context

int factor = 2;
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.forEach(n -> System.out.println(n * factor));
// factor cannot be changed after this!

5. Common mistakes when working with lambda expressions

Error #1: attempting to modify a variable captured by a lambda.
This code won’t compile — the variable must be effectively final:

int sum = 0;
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.forEach(n -> sum += n); // Error: sum is not final!

If you need to accumulate values, use an array or a wrapper object.

Error #2: confusion about the scope of this.
Inside a lambda expression, this refers to the outer class, not to the lambda (unlike in an anonymous class).

Error #3: forgetting braces and return in a multi-line lambda expression.
If the lambda body isn’t a single expression, you need braces and return:

Function<Integer, Integer> square = x -> {
    int y = x * x;
    return y;
};

Error #4: incorrect type definition of a lambda expression.
A lambda expression always implements a functional interface. You can’t just write:

var f = x -> x + 1; // Error! The interface type is unknown.

You need to specify the type explicitly:

Function<Integer, Integer> f = x -> x + 1;
1
Task
JAVA 25 SELF, level 21, lesson 0
Locked
System voice announcement 📣
System voice announcement 📣
1
Task
JAVA 25 SELF, level 21, lesson 0
Locked
User Input Standardization 📝
User Input Standardization 📝
1
Task
JAVA 25 SELF, level 21, lesson 0
Locked
Athletes ranking by "punch power" 🥊
Athletes ranking by "punch power" 🥊
1
Task
JAVA 25 SELF, level 21, lesson 0
Locked
Dynamic Report Generation 📊
Dynamic Report Generation 📊
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION