Modifying local variable from inside lambda

JavaLambdaJava 8

Java Problem Overview


Modifying a local variable in forEach gives a compile error:

Normal

    int ordinal = 0;
    for (Example s : list) {
        s.setOrdinal(ordinal);
        ordinal++;
    }

With Lambda

    int ordinal = 0;
    list.forEach(s -> {
        s.setOrdinal(ordinal);
        ordinal++;
    });

Any idea how to resolve this?

Java Solutions


Solution 1 - Java

Use a wrapper

Any kind of wrapper is good.

With Java 10+, use this construct as it's very easy to setup:

var wrapper = new Object(){ int ordinal = 0; };
list.forEach(s -> {
  s.setOrdinal(wrapper.ordinal++);
});

With Java 8+, use either an AtomicInteger:

AtomicInteger ordinal = new AtomicInteger(0);
list.forEach(s -> {
  s.setOrdinal(ordinal.getAndIncrement());
});

... or an array:

int[] ordinal = { 0 };
list.forEach(s -> {
  s.setOrdinal(ordinal[0]++);
});

Note: be very careful if you use a parallel stream. You might not end up with the expected result. Other solutions like Stuart's might be more adapted for those cases.

For types other than int

Of course, this is still valid for types other than int.

For instance, with Java 10+:

var wrapper = new Object(){ String value = ""; };
list.forEach(s->{
  wrapper.value += "blah";
});

Or if you're stuck with Java 8 or 9, use the same kind of construct as we did above, but with an AtomicReference...

AtomicReference<String> value = new AtomicReference<>("");
list.forEach(s -> {
  value.set(value.get() + s);
});

... or an array:

String[] value = { "" };
list.forEach(s-> {
  value[0] += s;
});

Solution 2 - Java

This is fairly close to an XY problem. That is, the question being asked is essentially how to mutate a captured local variable from a lambda. But the actual task at hand is how to number the elements of a list.

In my experience, upward of 80% of the time there is a question of how to mutate a captured local from within a lambda, there's a better way to proceed. Usually this involves reduction, but in this case the technique of running a stream over the list indexes applies well:

IntStream.range(0, list.size())
         .forEach(i -> list.get(i).setOrdinal(i));

Solution 3 - Java

If you only need to pass the value from the outside into the lambda, and not get it out, you can do it with a regular anonymous class instead of a lambda:

list.forEach(new Consumer<Example>() {
    int ordinal = 0;
    public void accept(Example s) {
        s.setOrdinal(ordinal);
        ordinal++;
    }
});

Solution 4 - Java

As the used variables from outside the lamda have to be (implicitly) final, you have to use something like AtomicInteger or write your own data structure.

See https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#accessing-local-variables.

Solution 5 - Java

An alternative to AtomicInteger is to use an array (or any other object able to store a value):

final int ordinal[] = new int[] { 0 };
list.forEach ( s -> s.setOrdinal ( ordinal[ 0 ]++ ) );

But see the Stuart's answer: there might be a better way to deal with your case.

Solution 6 - Java

Yes, you can modify local variables from inside lambdas (in the way shown by the other answers), but you should not do it. Lambdas have been made for functional style of programming and this means: No side effects. What you want to do is considered bad style. It is also dangerous in case of parallel streams.

You should either find a solution without side effects or use a traditional for loop.

Solution 7 - Java

If you are on Java 10, you can use var for that:

var ordinal = new Object() { int value; };
list.forEach(s -> {
    s.setOrdinal(ordinal.value);
    ordinal.value++;
});

Solution 8 - Java

You can wrap it up to workaround the compiler but please remember that side effects in lambdas are discouraged.

To quote the javadoc

> Side-effects in behavioral parameters to stream operations are, in general, discouraged, as they can often lead to unwitting violations of the statelessness requirement > A small number of stream operations, such as forEach() and peek(), can operate only via side-effects; these should be used with care

Solution 9 - Java

I had a slightly different problem. Instead of incrementing a local variable in the forEach, I needed to assign an object to the local variable.

I solved this by defining a private inner domain class that wraps both the list I want to iterate over (countryList) and the output I hope to get from that list (foundCountry). Then using Java 8 "forEach", I iterate over the list field, and when the object I want is found, I assign that object to the output field. So this assigns a value to a field of the local variable, not changing the local variable itself. I believe that since the local variable itself is not changed, the compiler doesn't complain. I can then use the value that I captured in the output field, outside of the list.

Domain Object:

public class Country {

    private int id;
	private String countryName;
	
	public Country(int id, String countryName){
		this.id = id;
		this.countryName = countryName;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getCountryName() {
		return countryName;
	}

	public void setCountryName(String countryName) {
		this.countryName = countryName;
	}
}

Wrapper object:

private class CountryFound{
	private final List<Country> countryList;
	private Country foundCountry;
	public CountryFound(List<Country> countryList, Country foundCountry){
		this.countryList = countryList;
		this.foundCountry = foundCountry;
	}
	public List<Country> getCountryList() {
		return countryList;
	}
	public void setCountryList(List<Country> countryList) {
		this.countryList = countryList;
	}
	public Country getFoundCountry() {
		return foundCountry;
	}
	public void setFoundCountry(Country foundCountry) {
		this.foundCountry = foundCountry;
	}
}

Iterate operation:

int id = 5;
CountryFound countryFound = new CountryFound(countryList, null);
countryFound.getCountryList().forEach(c -> {
	if(c.getId() == id){
		countryFound.setFoundCountry(c);
	}
});
System.out.println("Country found: " + countryFound.getFoundCountry().getCountryName());

You could remove the wrapper class method "setCountryList()" and make the field "countryList" final, but I did not get compilation errors leaving these details as-is.

Solution 10 - Java

To have a more general solution, you can write a generic Wrapper class:

public static class Wrapper<T> {
	public T obj;
	public Wrapper(T obj) { this.obj = obj; }
}
...
Wrapper<Integer> w = new Wrapper<>(0);
this.forEach(s -> {
    s.setOrdinal(w.obj);
    w.obj++;
});

(this is a variant of the solution given by Almir Campos).

In the specific case this is not a good solution, as Integer is worse than int for your purpose, anyway this solution is more general I think.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPatanView Question on Stackoverflow
Solution 1 - JavaOlivier GrégoireView Answer on Stackoverflow
Solution 2 - JavaStuart MarksView Answer on Stackoverflow
Solution 3 - JavanewacctView Answer on Stackoverflow
Solution 4 - JavafloView Answer on Stackoverflow
Solution 5 - JavazakmckView Answer on Stackoverflow
Solution 6 - JavaDonatView Answer on Stackoverflow
Solution 7 - JavaZhekaKozlovView Answer on Stackoverflow
Solution 8 - JavacodemonkeyView Answer on Stackoverflow
Solution 9 - JavaSteve TView Answer on Stackoverflow
Solution 10 - Javaluca.vercelliView Answer on Stackoverflow