Explain Codes LogoExplain Codes Logo

Modifying local variable from inside lambda

java
lambda
best-practices
mutable-state
Alex KataevbyAlex Kataev·Feb 28, 2025
TLDR

Modifying local variables directly within lambda expressions is forbidden as they are required to be effectively final. To alter a value, yet stick to this rule, you can wrap the variable in an array or use a concurrent class such as AtomicInteger:

AtomicInteger counter = new AtomicInteger(); stream.forEach(item -> counter.incrementAndGet()); // Gathering sheep in the pen

This method works since it changes the value, not the immutable reference itself.

Wrapping variables

Immutable local variables can appear "changeable" in lambdas if encased in a wrapper class. The mutable wrapper gives you a sneaky workaround:

AtomicReference<String> sneaky = new AtomicReference<>("initial"); stream.forEach(item -> sneaky.set(item)); // Changing costumes inside the wrapper

The array trick

An array can play the same trick. Though the array reference stays the same, the values it stores can alter:

final String[] wrapper = new String[1]; wrapper[0] = "initial"; stream.forEach(item -> wrapper[0] = item); // It's bigger on the inside

You can even craft your own mutable holder to really personalize this approach.

Anonymous power with Java 10+

Java 10 gave us var. Combine this language feature with anonymous classes to encapsulate mutable state:

var chameleon = new Object() { int count = 0; }; stream.forEach(item -> chameleon.count++); // Ninja mutation hidden inside

Fancy stream-based iteration

By using IntStream.range, you can mutate elements in a fancy, stream-based approach:

IntStream.range(0, array.length).forEach(i -> array[i]++); // I'm feeling 22!

Play safe with parallel streams

When dealing with parallel streams, it's important to be thread-safe. Multiple threads meddling with your variable can result in chaos — like adding too many chefs to a soup:

ConcurrentHashMap map = new ConcurrentHashMap(); // Because two threads at the same soup bowl is a recipe for disaster

Reduction: another way out

Rather than mutating local variables, consider reduction techniques. It's like wrangling your results into a single, neat summary:

int sum = IntStream.range(1, 10).reduce(0, Integer::sum); // Hey, gals, let's bunch in!

Are you asking the right question?

Sometimes striving to mutate a local variable within a lambda hints at an XY problem. Looking sideways at the problem might lead to simpler, more elegant solutions.

Thinking outside the sandbox

If AtomicInteger or AtomicReference don't suit, it might be worth shielding the mutable state within a mutable class or leverage collectors to sidestep the need to mutate local variables.

Heed those best practices

Remember, lambdas love immutable state, and cleaner, more maintainable code can often be achieved by masterfully avoiding mutations altogether.