JavaRush /Курсы /JAVA 25 SELF /Лямбда-выражения: синтаксис, области видимости

Лямбда-выражения: синтаксис, области видимости

JAVA 25 SELF
48 уровень , 0 лекция
Открыта

1. Введение

Давайте честно: писать анонимные классы ради одной-двух строчек кода — это как нанимать огромный грузовик, чтобы перевезти из пекарни в магазин одну булочку.

Например, если вы хотите отсортировать список строк по длине, до Java 8 приходилось писать вот так:

List<String> list = Arrays.asList("яблоко", "банан", "киви");
Collections.sort(list, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

Вроде бы задача простая, а кода — на полэкрана. Особенно раздражает, когда таких операций много: код становится «шумным», теряется суть задачи. Программисты плакали, страдали, а потом придумали лямбда-выражения. Мы уже немного их учили, теперь же будем повторять и углублять наши знания.

Что такое лямбда-выражение

Лямбда-выражение — это компактная форма записи реализации функционального интерфейса, то есть интерфейса с единственным абстрактным методом (например, Comparator, Runnable, Consumer и многие другие).

Проще говоря, лямбда-выражение позволяет вам написать функцию «на лету», прямо там, где она нужна, без объявления отдельного класса или метода.

Общий синтаксис:

(параметры) -> { тело }

Примеры:

  • (a, b) -> a + b — функция сложения двух чисел
  • x -> x * x — функция возведения числа в квадрат
  • () -> System.out.println("Hello!") — функция без параметров

Связь с функциональными интерфейсами:
Лямбда-выражение всегда можно присвоить переменной типа функционального интерфейса или передать как аргумент в метод, который ожидает такой интерфейс.

2. Синтаксис лямбда-выражений

Без параметров

Runnable r = () -> System.out.println("Привет, мир!");
r.run(); // Выведет: Привет, мир!

Один параметр
Если параметр один, скобки можно опустить:

Consumer<String> print = s -> System.out.println(s);
print.accept("Java — это круто!");

Несколько параметров
Скобки обязательны:

Comparator<String> cmp = (a, b) -> a.length() - b.length();

Тело из одного выражения
Если тело состоит из одного выражения, фигурные скобки и return не нужны:

Function<Integer, Integer> square = x -> x * x;
System.out.println(square.apply(5)); // 25

Тело из блока
Если нужно несколько инструкций, используйте фигурные скобки и return (если есть возвращаемое значение):

Function<Integer, Integer> abs = x -> {
    if (x < 0) {
        return -x;
    }
    return x;
};
System.out.println(abs.apply(-3)); // 3

Типы параметров
Чаще всего типы параметров можно не указывать — компилятор сам догадается из контекста. Но если хочется, можно явно:

Comparator<String> cmp = (String a, String b) -> a.length() - b.length();

Лямбда без возвращаемого значения
Если интерфейс возвращает void, просто пишите инструкции:

list.forEach(s -> System.out.println("Элемент: " + s));

Таблица: Варианты синтаксиса лямбда-выражений

Что делаем Пример Комментарий
Без параметров
() -> System.out.println("Hi")
Например, для Runnable
Один параметр
x -> x * x
Можно без скобок
Несколько параметров
(a, b) -> a + b
Скобки обязательны
Одно выражение
x -> x + 1
Без return и фигурных скобок
Блок кода
x -> { int y = x + 1; return y * 2; }
С return, если есть результат

3. Применение: где и как использовать лямбда-выражения

Лямбда-выражения чаще всего используют там, где требуется передать «поведение» — функцию — в качестве аргумента. Это стало настоящей революцией для коллекций, потоков (Stream API), событий и многого другого.

Сортировка списка

До Java 8:

list.sort(new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

С лямбда-выражением:

list.sort((a, b) -> a.length() - b.length());

Создание потока (Thread)

Thread t = new Thread(() -> System.out.println("Поток запущен!"));
t.start();

Обработка коллекций

list.forEach(s -> System.out.println(s.toUpperCase()));

Фильтрация списка

List<String> longWords = list.stream()
    .filter(s -> s.length() > 5)
    .collect(Collectors.toList());

Пример: Развиваем наше учебное приложение

Допустим, у нас есть список пользователей:

List<String> users = Arrays.asList("Alice", "Bob", "Charlie");

Вывести всех пользователей, имена которых длиннее 4 символов:

users.stream()
    .filter(name -> name.length() > 4)
    .forEach(name -> System.out.println("Пользователь: " + name));

4. Область видимости переменных в лямбда-выражениях

Лямбда-выражения могут использовать переменные из окружающего метода, но есть нюансы!

Переменные и лямбды

Лямбда в Java может «захватывать» только такие переменные, которые после инициализации больше не меняются. Если переменная объявлена с final — всё очевидно. Но даже если слово final не написано, компилятор сам проверяет: меняется значение или нет. Если нет, то он считает её «как будто финальной» и спокойно допускает в лямбду.

Пример:

int minLength = 4; // значение нигде не меняется
users.forEach(name -> {
    if (name.length() > minLength) {
        System.out.println(name);
    }
});

Здесь всё работает, потому что minLength остаётся тем же самым числом.

Но если после использования в лямбде вы попытаетесь переприсвоить minLength, то получите ошибку компиляции:

int minLength = 4;
users.forEach(name -> {
    if (name.length() > minLength) {
        System.out.println(name);
    }
});
minLength = 10; // Ошибка! Лямбда уже "зафиксировала" значение

По сути правило очень простое: переменная, попавшая в лямбду, должна быть неизменяемой.

Отличие от анонимных классов

В анонимных классах и лямбда-выражениях переменные из внешнего метода работают одинаково: только final/эффективно final.

НО!
У лямбда-выражения this ссылается на внешний объект (текущий экземпляр класса), а у анонимного класса — на экземпляр анонимного класса. Это важно, если вы внутри лямбды обращаетесь к полям или методам текущего класса.

Пример:

public class Example {
    String name = "Внешний класс";

    void demo() {
        Runnable r1 = new Runnable() {
            String name = "Анонимный класс";
            @Override
            public void run() {
                System.out.println(this.name); // "Анонимный класс"
            }
        };

        Runnable r2 = () -> System.out.println(this.name); // "Внешний класс"

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

5. Типичные ошибки при работе с лямбда-выражениями

Ошибка №1: Использование не final/неэффективно final переменной. Если вы решили изменить переменную после того, как использовали её в лямбде, компилятор тут же вас осадит. Это делается для безопасности: иначе было бы непонятно, какое значение переменной использовать.

Ошибка №2: Путаница с this. В лямбда-выражении this — это внешний класс, а в анонимном классе — сам анонимный класс. Если вы пытаетесь вызвать метод внешнего класса из лямбды, всё будет работать, а вот из анонимного класса — нет (если вы рассчитывали на контекст внешнего класса).

Ошибка №3: Лямбда без контекста. Лямбда-выражение нельзя использовать само по себе — его нужно либо присвоить переменной функционального интерфейса, либо передать туда, где этот интерфейс ожидается. Попытка просто написать x -> x + 1 вне контекста вызовет ошибку.

Ошибка №4: Слишком сложная лямбда. Если лямбда-выражение становится длиннее 3–5 строк, его сложно читать. В таких случаях лучше вынести логику в отдельный метод.

1
Задача
JAVA 25 SELF, 48 уровень, 0 лекция
Недоступна
Мастерская по сортировке названий 📜
Мастерская по сортировке названий 📜
1
Задача
JAVA 25 SELF, 48 уровень, 0 лекция
Недоступна
Мистический классификатор чисел 🔮
Мистический классификатор чисел 🔮
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Andrey Уровень 1
12 октября 2025
48