199

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?

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
Patan
  • 17,073
  • 36
  • 124
  • 198
  • What is the compiler error? Please include that in your question, and see if you can narrow your example code down so you can include a complete class definition, or at least a complete method definition, that reproduces the error. – Andrew Janke May 04 '15 at 09:35
  • 18
    Considering lambdas are essentially syntactic sugar for an anonymous inner class, my intuition is that it is impossible to capture a non final, local variable. I'd love to be proved wrong though. – Sinkingpoint May 04 '15 at 09:36
  • 3
    A variable used in a lambda expression must be effectively final. You could use an atomic integer although it's overkill, so a lambda expression is not really needed here. Just stick with the for-loop. – Alexis C. May 04 '15 at 09:36
  • 3
    The variable must be *effectively final*. See this: [Why the restriction on local variable capture?](http://www.lambdafaq.org/what-are-the-reasons-for-the-restriction-to-effective-immutability/) – Jesper May 04 '15 at 09:43
  • 2
    possible duplicate of [Why do java 8 lambdas allow access to non-final class variables?](http://stackoverflow.com/questions/29029849/why-do-java-8-lambdas-allow-access-to-non-final-class-variables) – MT0 May 04 '15 at 09:44
  • 4
    @Quirliom They aren't syntactic sugar for anonymous classes. Lambdas use method handles under the hood – Vince May 04 '15 at 21:17
  • @VinceEmigh: but *semantically* they are equivalent to a restricted subset of anonymous classes for all effective purposes – newacct May 05 '15 at 19:27
  • from java 8 in action : "local variables have to be explicitly declared final or are effectively final. In other words lambda expressions can capture local variables that are assigned to them only once" – rohith Feb 26 '16 at 04:08
  • Please, take a look at this answer: https://stackoverflow.com/a/53350417/2457251 Maybe not the most elegant, or even the most correct, but it will do. – Almir Campos Nov 17 '18 at 10:41

10 Answers10

305

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;
});
Olivier Grégoire
  • 33,839
  • 23
  • 96
  • 137
  • Why are you allowed to use an array like `int[] ordinal = { 0 };`? Can you explain it. Thank you – mrbela May 30 '19 at 17:26
  • @mrbela What exactly don't you understand? Maybe I can clarify that specific bit? – Olivier Grégoire May 30 '19 at 20:16
  • 8
    @mrbela arrays are passed by reference. When you pass in an array, you are actually passing in a memory address for that array. Primitive types like integer are sent by value, which means a copy of the value is passed in. Pass-by-value there is no relationship between the original value and the copy sent into your methods - manipulating the copy does nothing to the original. Passing by reference means the original value and the one sent into the method are one in the same - manipulating it in your method will change the value outside of the method. – DetectivePikachu Jul 09 '19 at 13:24
  • @Olivier Grégoire this really saved my hide. I used the Java 10 solution with "var". Can you explain how this works? I've Googled everywhere and this is the only place I've found with this particular usage. My guess is that since a lambda only allows (effectively) final objects from outside its scope, the "var" object basically fools the compiler into inferring that it's final, since it assumes that only final objects will be referenced outside scope. I don't know, that's my best guess. If you'd care to provide an explanation I'd appreciate it, since I like to know why my code works :) – oaker Feb 17 '20 at 19:56
  • 1
    @oaker This works because Java will create the class MyClass$1 as follows: `class MyClass$1 { int value; }` In a usual class, this means that you create a class with a package-private variable named `value`. This is the same, but the class is actually anonymous to us, not to the compiler or the JVM. Java will compile the code as if it was `MyClass$1 wrapper = new MyClass$1();`. And the anonymous class becomes just another class. In the end, we just added syntactical sugar on top of it to make it readable. Also, the class is an inner class with package-private field. That field is usable. – Olivier Grégoire Feb 20 '20 at 10:12
  • @OlivierGrégoire, do you really need an AtomicReference? – Pacerier May 26 '20 at 00:13
  • @Pacerier No. My first sentence even says so: "Any kind of wrapper is good." If you read the whole answer, you'll see that there are several alternatives. – Olivier Grégoire May 26 '20 at 07:17
22

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));
Community
  • 1
  • 1
Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
  • 3
    Good solution but only if `list` is a `RandomAccess` list – ZhekaKozlov Nov 05 '17 at 17:53
  • I would be curious to know if the problem of progression message every k iterations can be practically solved without a dedicated counter, ie, this case: stream.forEach ( e -> { doSomething(e); if ( ++ctr % 1000 == 0 ) log.info ( "I've processed {} elements", ctr ); } I don't see a more practical way (reduction could do it, but would be more verbose, especially with parallel streams). – zakmck Mar 12 '19 at 12:25
14

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++;
    }
});
newacct
  • 119,665
  • 29
  • 163
  • 224
  • 2
    ... and what if you need to actually read the result? The result is not visible to code at the top level. – Luke Usherwood Jan 07 '19 at 14:03
  • @LukeUsherwood: You're right. This is only for if you only need to pass data from the outside into the lambda, not get it out. If you need to get it out, you would need to have the lambda capture a reference to a mutable object, e.g. an array, or an object with non-final public fields, and pass data by setting it into the object. – newacct Jan 12 '19 at 17:09
8

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.

flo
  • 9,713
  • 6
  • 25
  • 41
7

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.

zakmck
  • 2,715
  • 1
  • 37
  • 53
4

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++;
});
ZhekaKozlov
  • 36,558
  • 20
  • 126
  • 155
4

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.

Donat
  • 4,157
  • 3
  • 11
  • 26
2

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

codemonkey
  • 608
  • 6
  • 7
0

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.

Steve T
  • 165
  • 11
0

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.

luca.vercelli
  • 898
  • 7
  • 24