JavaRush/Курсы/JAVA 25 SELF/Введение в лямбда-выражения

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

Открыта

1. Знакомство

Если коротко: лямбда-выражение — это способ быстро создать реализацию функционального интерфейса "на лету", не объявляя отдельный класс или анонимный класс. Это как мини-метод без имени, который можно передать как аргумент или сохранить в переменную.

Java долгое время была «классическим» ООП-языком. Если вы хотели передать кусочек поведения, например, что делать при нажатии кнопки, нужно было писать анонимные классы:

button.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick() {
        System.out.println("Кнопка нажата!");
    }
});

С Java 8 появилась лямбда-магия:

button.setOnClickListener(() -> System.out.println("Кнопка нажата!"));

Где () -> System.out.println("Кнопка нажата!") — это и есть лямбда-выражение.

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

Почему это важно?

  • Передавать поведение как значение (например, что делать с каждым элементом списка).
  • Писать компактный и читаемый код.
  • Использовать современные API Java: Stream API, обработку событий, асинхронные задачи и многое другое.

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

Общий вид

(параметры) -> выражение
// или
(параметры) -> { блок кода }

Примеры для разных интерфейсов

1. Без параметров (например, Runnable):

Runnable r = () -> System.out.println("Привет, лямбда!");
r.run();

2. Один параметр (например, Consumer):

Consumer<String> printer = s -> System.out.println("Вы передали: " + s);
printer.accept("Java");

Если параметр только один и его тип можно вывести, скобки можно опустить.
Тип String — это тип переменной (s), которую мы туда передаем.

3. Несколько параметров (например, Comparator):

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

Тип Integer — это тип переменных (a, b), которые мы туда передаем.

4. Многострочное тело (нужны фигурные скобки и return, если есть явный возврат):

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

Первый Integer — это тип результата функции, второй Integer — это тип параметра.

Сокращения и лаконичность

  • Если тело — одно выражение, скобки и return можно опустить.
  • Если параметров нет — пишем пустые скобки: () -> ...
  • Если один параметр — можно без скобок: x -> ...
  • Если больше одного параметра — нужны скобки: (a, b) -> ...

Таблица-сводка

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

Интерфейс Лямбда-выражение (пример) Эквивалент анонимного класса
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; } }

Пример с собственным интерфейсом

Пусть у нас есть функциональный интерфейс:

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

Раньше реализовывали так:

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

Теперь — современно:

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

Примеры для стандартных интерфейсов

Runnable:

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

Comparator:

List<String> list = Arrays.asList("яблоко", "банан", "киви");
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. Область видимости и захват переменных

Переменные из внешнего контекста (effectively final)

Лямбда-выражения могут использовать переменные из окружающего метода. Но есть правило: такие переменные должны быть effectively final — то есть либо явно объявлены как final, либо просто не изменяться после инициализации.

Пример:

String prefix = "Результат: ";
Function<Integer, String> f = x -> prefix + (x * 2);
// prefix здесь "заморожен" — после этого его менять нельзя
System.out.println(f.apply(5)); // Результат: 10

Если попытаться изменить prefix после того, как он использован в лямбде, компилятор выдаст ошибку.

Почему так?
Лямбда-выражение может быть вызвано после выхода из метода, где была объявлена переменная. Чтобы избежать «магических» багов, Java разрешает использовать только неизменяемые переменные.

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

В анонимных классах действует то же правило: переменные из внешнего метода должны быть final/effectively final. Но есть нюансы с областью видимости: внутри анонимного класса this ссылается на сам анонимный класс, а в лямбде — на внешний класс.

public class Demo {
    public void test() {
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println(this); // Выведет: Demo$1 (анонимный класс)
            }
        };

        Runnable r2 = () -> System.out.println(this); // Выведет: Demo (внешний класс)

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

Лямбда и поля класса

Лямбда-выражение может обращаться к полям внешнего класса без ограничений:

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));
    }
}

Здесь base — можно менять (это поле класса).
x — должен быть effectively final.

4. Практика: напишем несколько лямбда-выражений

Пример: фильтрация списка чисел

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

nums.stream()
    .filter(n -> n % 2 == 0)
    .forEach(n -> System.out.println("Чётное: " + n));

Подробнее про Stream API вы узнаете на 30-м уровне :P

Пример: функция преобразования строки

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

Пример: свой функциональный интерфейс

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

StringTransformer exclaim = s -> s + "!";
System.out.println(exclaim.transform("Привет")); // Привет!

Пример: использование переменных из внешнего контекста

int factor = 2;
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.forEach(n -> System.out.println(n * factor));
// factor нельзя изменить после этого!

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

Ошибка №1: попытка изменить переменную, захваченную лямбдой.
Такой код не скомпилируется — переменная должна быть effectively final:

int sum = 0;
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.forEach(n -> sum += n); // Ошибка: sum не final!

Если нужно накапливать значения — используйте массив или объект-обёртку.

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

Ошибка №3: забыли фигурные скобки и return в многострочном лямбда-выражении.
Если тело лямбды — не одно выражение, нужны скобки и return:

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

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

var f = x -> x + 1; // Ошибка! Неизвестно, какой тип интерфейса.

Нужно явно указать тип:

Function<Integer, Integer> f = x -> x + 1;
1
Задача
JAVA 25 SELF,  21 уровень,  0 лекция
Недоступна
Голосовое объявление системы 📣
Голосовое объявление системы 📣
1
Задача
JAVA 25 SELF,  21 уровень,  0 лекция
Недоступна
Стандартизация пользовательского ввода 📝
Стандартизация пользовательского ввода 📝
1
Задача
JAVA 25 SELF,  21 уровень,  0 лекция
Недоступна
Рейтинг спортсменов по "силе удара" 🥊
Рейтинг спортсменов по "силе удара" 🥊
1
Задача
JAVA 25 SELF,  21 уровень,  0 лекция
Недоступна
Динамическое формирование отчётов 📊
Динамическое формирование отчётов 📊
Комментарии (12)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Ivan Kulazhenkov
Уровень 23
30 сентября, 15:12
Подробнее про Stream API вы узнаете на 30-м уровне :P Вайб взоврать javarush
16 мая, 13:54
чем дальше по лекциям иду, тем все меньше структуризации информации. + зачем в примерах то, о чем мы узнаем намного позже, если этот код самостоятельно мы все равно не прочитаем? я про Stream API
KaRtOfElIoJava Developer в Бомж
25 августа, 16:59
Полностью согласен, я больше примеры с ии размусоливал, чем лекцию читал
No Name
Уровень 22
22 февраля, 10:14
А как тут return что то возвращает, метод же типа void: Раньше реализовывали так: Operation sum = new Operation() { @Override public void apply(int a, int b) { return a + b; } };
Vladimir
Уровень 32
11 апреля, 19:14
раньше тоже нельзя было ничего возвращать в void-метод
Vatrysuku QA Engineer
8 июля, 12:50
Мне кажется, это опечатка, тоже долго тупила над этим примером
No Name
Уровень 22
20 февраля, 15:25
Мне так кажется будто лямбда это сокращение анонимного класса
No Name
Уровень 22
20 февраля, 15:25
Только лямбда на один метод
Zlyden'
Уровень 1
20 ноября 2025, 10:32
4. Многострочное тело (нужны фигурные скобки и return, если есть явный возврат): Function<Integer, Integer> square = x -> { int result = x * x; return result; }; System.out.println(square.apply(6)); // 36 Первый Integer — это тип результата функции, второй Integer — это тип параметра. Наоборот же, второй Integer - тип результата функции, а первый - тип параметра. 😡
Mike
Уровень 61
11 февраля, 09:41
верно подмечено ;) Подвис на этой теме и решил поизучать её глубже @FunctionalInterface public interface Function<T,R> ---- Type Parameters: T - the type of the input to the function R - the type of the result of the function ---- Method Details apply R apply(T t) Applies this function to the given argument. Parameters: t - the function argument Returns: the function result
Overbyaka
Уровень 1
11 февраля, 13:13
Да, тоже смутилась, когда в этой же лекции дают пример:
Function<String, Integer> parse = s -> Integer.parseInt(s);
System.out.println(parse.apply("123")); // 123
Andrey
Уровень 6
15 сентября 2025, 17:45
21