tgoop.com/javaarchivebooks/187
Create:
Last Update:
Last Update:
Что такое effectively final и что с ним делать
Начну с правильного ответа на вопрос выше. В точке Б мы получим предупреждение компилятора: local variables referenced from a lambda expression must be final or effectively final
В этом посте обсудим, что означает effectively final, о чём молчит спецификация и как менять переменные внутри лямбд.
Про модификатор final всё понятно — он запрещает изменение переменной
final int count = 100;
count
всегда будет равен 100. Каждый, кто напишет count = 200;будет осуждён компилятором. Для ссылок схема такая же:
final User admin = User.createAdmin();Ссылка
admin
всегда будет указывать на объект User с параметрами админа. Никто не может её переприсвоить:❌ admin = new User(…)Effectively final называется переменная, значение которой не меняется после инициализации. По сути это тот же final, но без ключевого слова.
Чтобы компилятор не ругался, надо выполнить два условия:
1️⃣ Локальная переменная однозначно определена до начала лямбда-выражения
Так не скомпилируется:
int x;Вот так норм:
if (…) х = 10
int x;2️⃣ Переменная не меняется внутри лямбды и после неё
if (…) х = 10; else х = 15;
int х = 10;❓ Зачем нужно такое ограничение?
…лямбда…
❌ х = 15
User user = …
…лямбда…
❌ user = userRepository.findByName(…)
✅ user.setTIN(…)
JLS 15.27.2 говорит, что ограничение помогает избежать многопоточных проблем: The restriction to effectively final variables prohibits access to dynamically-changing local variables, whose capture would likely introduce concurrency problems
С первого взгляда звучит разумно. Основное применение лямбд — в рамках Stream API. В Stream API есть опция parallel(), которая запускает выполнение в разных потоках. Там и возникнут concurrency problems.
Но я не принимаю это объяснение, потому что:
🤔 С каких пор компилятор волнуют многопоточные проблемы? Вся многопоточка отдана под контроль разработчика с начала времён
🤔 Если локальная переменная станет полем класса, то компилятор перестанет ругаться. При этом вероятность concurrency problems увеличится в разы
Моя гипотеза: требование final/effectively final связано с особенностями реализации лямбд и ограничением модели памяти. Это технические сложности в JVM и ничего больше. Отсутствие многопоточных проблем, о которых говорится в JLS, это всего лишь следствие, а не причина.
❓ Как же менять переменные внутри лямбд?
1️⃣ Сделать переменную полем класса:
int count;Не лучший вариант, переменная доступна теперь другим потокам. Concurrency problems!
public void m() {
list.forEach(v -> count++);
}
2️⃣ Использовать Atomic обёртку
Для примитивов:
AtomicInteger count = new AtomicInteger(0);Для ссылок:
list.forEach(v -> count.incrementAndGet())
AtomicReference<User> user = new AtomicReference<>();3️⃣ Использовать массив с одним элементом
…map(i -> user.set(…))
int[] res = new int[] {0};Популярный вариант, который подходит и для примитивов, и для ссылок. Но мне больше нравится вариант с Atomic:)
list.forEach(v -> res[0]++);
BY Уютное сообщество джавистов
Share with your friend now:
tgoop.com/javaarchivebooks/187