История вопроса:
До Java 8 для использования переменных из внешней области видимости во внутреннем классе или анонимном классе эти переменные обязательно должны были быть объявлены как final. В Java 8 было ослаблено требование: теперь переменная может быть не объявлена как final, если по факту она не изменяется (effectively final).
Проблема:
Лямбда-выражения и внутренние классы используют замыкания над переменными из внешнего блока. Однако если такие переменные меняют значение, возникает путаница и неправильное поведение — невозможно понять, какое значение использовать.
Решение:
Компилятор разрешает использовать переменную во внутреннем классе или лямбде только если она effectively final — то есть ни разу не изменялась после инициализации, даже если не объявлена явно как final.
Пример кода:
public void demo() { int x = 10; Runnable r = () -> System.out.println(x); // x — effectively final r.run(); }
Если попытаться изменить x:
public void demo() { int x = 10; x = 20; // теперь x не эффективно final Runnable r = () -> System.out.println(x); // Ошибка компиляции }
Ключевые особенности:
Можно ли использовать изменяемые объекты, если сама ссылка effectively final?
Да, если ссылка не меняется, но объёкт по этой ссылке меняется — это разрешено. Например,
List<String> list = new ArrayList<>(); list.add("A"); Runnable r = () -> System.out.println(list.get(0)); // ОК list = new ArrayList<>(); // Если так, будет ошибка компиляции
Можно ли объявить переменную как final, а потом всё ещё изменять содержимое объекта?
Да. final относится к ссылке, а не к содержимому объекта. Изменять состояние объекта по ссылке — допустимо. Например,
final List<Integer> nums = new ArrayList<>(); nums.add(5); // ОК nums = new ArrayList<>(); // Ошибка
Можно ли использовать переменные-аргументы метода (parameters) в лямбдах?
Да, если они также effectively final — то есть не изменяются внутри метода после инициализации.
В методе используются переменные, которые случайно перезаписываются после создания лямбды, в результате программа не компилируется и тратится время на выяснение причины.
Плюсы:
Минусы:
Разработчик использует увеличиваемое значение через AtomicInteger (или другой holder-объект), который хранит ссылку, а не значение, обеспечивая корректную работу лямбд, даже если требуется изменять счётчик внутри лямбда выражения.
Плюсы:
Минусы: