Изменение локальной переменной внутри lambda в Java

Пройдите тест, узнайте какой профессии подходите

Я предпочитаю
0%
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы

Быстрый ответ

В лямбда-выражениях Java прямые изменения локальных переменных невозможны, так как они должны быть фактически неизменяемы. Решить эту задачу можно с помощью использования массивов или специальных классов, вроде AtomicInteger:

Java
Скопировать код
AtomicInteger counter = new AtomicInteger();
stream.forEach(item -> counter.incrementAndGet()); // Cчитаем овец в стаде

С таким подходом изменяется значение, заключённое в объекте, при этом сама ссылка на объект остаётся неизменной.

Кинга Идем в IT: пошаговый план для смены профессии

Обход ограничений

Использование классов-обёрток

Неизменяемую локальную переменную можно поместить в контейнер, и тогда получится создать иллюзию изменяемости:

Java
Скопировать код
AtomicReference<String> sneaky = new AtomicReference<>("initial");
stream.forEach(item -> sneaky.set(item)); // Замена значения производится с помощью обёртки

Манипуляции с массивами

Вы можете использовать массивы, чтобы изменить данные, не нарушая при этом константности ссылки:

Java
Скопировать код
final String[] wrapper = new String[1];
wrapper[0] = "initial";
stream.forEach(item -> wrapper[0] = item); // Меняем содержимое массива, но ссылка остаётся прежней

Также возможно создать собственный класс-контейнер для более гибкого и индивидуализированного подхода.

Анонимные классы и var в Java 10+

С началом эры var в Java 10, стали доступны анонимные классы для инкапсуляции изменяемого состояния:

Java
Скопировать код
var chameleon = new Object() { int count = 0; };
stream.forEach(item -> chameleon.count++); // Изменяем состояние внутри анонимного класса

Потоковая итерация с использованием IntStream.range

Изменение элементов в коллекции можно производить стильно, применяя потоковую итерацию:

Java
Скопировать код
IntStream.range(0, array.length).forEach(i -> array[i]++); // Усовершенствуем процесс итерации

Потокобезопасность при работе с параллельными потоками

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

Java
Скопировать код
ConcurrentHashMap map = new ConcurrentHashMap(); 
// Много поваров могут испортить бульон

Сведение: стратегия агрегирования

В определённых ситуациях вместо изменения локальных переменных предпочтительнее применять стратегии сведения, собирая результаты в один итоговый показатель:

Java
Скопировать код
int sum = IntStream.range(1, 10).reduce(0, Integer::sum); // Консолидируем результаты в единое целое

Визуализация

Представьте, что лямбда — это песочница, где можно модифицировать игрушки, но вынести их оттуда — невозможно.

Локальная переменная (снаружи):

Markdown
Скопировать код
🏠 [🚗🚕] (Машины оставляем дома, из песочницы их не вынесешь)

Лямбда (внутри песочницы):

Java
Скопировать код
() -> { 
  // Здесь можно перекрасить 🚗,
  // но новую из дома 🏠 принести не получится.
}

После лямбды:

Markdown
Скопировать код
🏠 [🚗🚕] (Те же машинки, только возможно, перекрашенные)

Важно: Мы можем изменить внешний вид игрушки, но заменить саму игрушку — нет.

Правильно ли вы задаёте вопрос?

Порой желание изменить локальную переменную в лямбда может быть признаком проблемы XY. Возможно, стоит пересмотреть задачу в поисках более упрощённого и элегантного решения.

Размышления за рамками

Если AtomicInteger или AtomicReference не подходят для ваших целей, стоит подумать о возможности организации изменяемого состояния в отдельном классе или применение коллекторов для избегания необходимости изменений локальных переменных.

Cледование лучшим практикам

Лямбда-выражения наиболее эффективно работают с неизменяемым состоянием, и зачастую простой и легко поддерживаемый код достигается посредством отказа от мутаций.

Полезные материалы

  1. Лямбда-выражения (учебные материалы по Java™) – принципы лямбда-выражений в Java.
  2. Изменение локальной переменной внутри лямбда – Stack Overflow – обсуждение изменения локальных переменных в лямбда-выражениях.
  3. Java SE 8: Быстрый старт с лямбда – краткое ознакомление с использованием лямбда в Java 8.
  4. Документация JDK 21 – Главная — информация о новейших функциях Java, включая лямбда-выражения.
  5. Понимание синтаксиса лямбда Java 8 – подробный анализ синтаксиса лямбда-выражений Java 8.
Свежие материалы