Каковы правила доступа лямбда-выражения в Java к локальным переменным внешнего метода?

«Каковы правила доступа лямбда-выражения в Java к локальным переменным внешнего метода?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Лямбда-выражение может обращаться к локальным переменным, параметрам метода или переменным класса, но с одним строгим ограничением: локальные переменные должны быть effectively final.

Правило effectively final: Переменная считается effectively final, если она не изменяет своего значения после инициализации (даже если ключевое слово final не указано явно).

Пример:

public void example() {
    int localVar = 10;          // effectively final
    final int explicitFinal = 20; // явно final
    int notFinal = 30;
    notFinal = 40;              // Изменение значения -> НЕ effectively final

    Runnable r = () -> {
        System.out.println(localVar);       // OK
        System.out.println(explicitFinal);  // OK
        // System.out.println(notFinal);    // ОШИБКА компиляции!
    };
    r.run();
}

Причина ограничения: Лямбда может выполняться в другом потоке и после завершения метода, в котором она была объявлена. Локальные переменные хранятся в стеке и уничтожаются вместе с фреймом метода. Чтобы обеспечить доступ, Java захватывает значение переменной на момент создания лямбды. Если бы переменная могла меняться, возникли бы проблемы с видимостью изменений между потоками (потребовалась бы синхронизация). Захват по значению делает поведение предсказуемым и безопасным.

Поля класса (нестатические) не имеют этого ограничения, так как лямбда захватывает ссылку на объект this.