Ответ
Effectively final — это переменная, которая не объявлена как final, но никогда не изменяется после инициализации. Это понятие критически важно для работы с лямбда-выражениями и анонимными классами, поскольку они могут захватывать из окружающей области видимости только final или effectively final переменные.
Почему такое ограничение?
- Потокобезопасность: Захваченная копия переменной должна быть неизменяемой, чтобы избежать race condition между внешним потоком и лямбдой.
- Семантика захвата: Лямбда может выполняться позже и в другом потоке. Если бы переменная могла меняться, её значение стало бы непредсказуемым.
Пример:
int threshold = 100; // effectively final
List<String> filtered = items.stream()
.filter(item -> item.length() > threshold) // Захват переменной
.toList();
// threshold = 200; // Ошибка компиляции, если раскомментировать!
// // Переменная перестанет быть effectively final.
Сравнение с явным final:
final int x = 10; // Явно final
int y = 20; // Effectively final (не меняется)
int z = 30;
z = 40; // z НЕ effectively final
Runnable r1 = () -> System.out.println(x + y); // OK
Runnable r2 = () -> System.out.println(z); // Ошибка компиляции!
Практическое значение:
- Позволяет писать более чистый код, не загромождая его ключевым словом
final. - Компилятор сам определяет, можно ли безопасно захватить переменную.
- Применяется также к анонимным классам (до Java 8) и локальным классам.
Исключение для изменяемых объектов: Важно: правило касается ссылки, а не состояния объекта.
List<String> list = new ArrayList<>(); // effectively final ссылка
list.add("item"); // OK — меняется состояние объекта, а не сама ссылка `list`
Runnable r = () -> System.out.println(list.size()); // Корректно
Итог: Концепция effectively final упрощает синтаксис, обеспечивая при этом безопасность доступа к данным из замыканий в многопоточной среде.