TGViewer
Java: fill the gaps Java: fill the gaps @java_fillthegaps · 12.3K subscribers
Post #458 7.07K
Что такое 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;
if (…) х = 10; else х = 15;

2️⃣ Переменная не меняется внутри лямбды и после неё

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;
public void m() {
list.forEach(v -> count++);
}

Не лучший вариант, переменная доступна теперь другим потокам. Concurrency problems!

2️⃣ Использовать Atomic обёртку

Для примитивов:
AtomicInteger count = new AtomicInteger(0);
list.forEach(v -> count.incrementAndGet())

Для ссылок:
AtomicReference<User> user = new AtomicReference<>();
…map(i -> user.set(…))

3️⃣ Использовать массив с одним элементом

int[] res = new int[] {0};
list.forEach(v -> res[0]++);

Популярный вариант, который подходит и для примитивов, и для ссылок. Но мне больше нравится вариант с Atomic:)
  • 🔥 77
  • 👍 50
  • 👎 8
  • ❤ 1
More from @java_fillthegaps
  1. Apr 30, 2026Get Your Hands Dirty on Clean Architecture: отзыв на книгу Когда я подняла тему чистой арх…
  2. Apr 27, 2026​Clean Architecture: отзыв на книгу Наконец-то дочитала книгу Clean Architecture Роберта М…
  3. Mar 4, 2026Как переиспользовать контекст в интеграционных тестах Сегодня расскажу базовый минимум для…
  4. Mar 4, 2026Post #660
  5. Mar 4, 2026Тестовый контекст поднимается 2 минуты. У нас 4 класса с интеграционными тестами, их конфи…
  6. Feb 25, 2026Чистая архитектура. Главы 3-5 Продолжаем спидран по Clean Architecture Роберта Мартина. ⭐️…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →