6

In Java I have the following code

List<Integer> myList = new ArrayList<>();
for (int i=0;i<9;i++) {
    myList.add(i);
}

Integer sum = 0;

myList.forEach(i -> {
    sum = sum + i; // does not compile, sum needs to be final or effectively final
});   

for(int i : myList) {
    sum = sum + i; //runs without problems
} 

My question is, why is it exactly that I cannot change the value of sum from within the lambda? It does the exact same thing as the for loop down below, or am I wrong? Interesting is also the fact that if I declare the Integer sum outside of the main method as static, it works as well. Can anyone explain me why?

EDIT: In another similar question Does Java 8 support closures, the answer seems to be :

it is a combination of backwards compatibility and project resourcing constraints.

However I still cannot understand why it works if I make sum an array or if I declare it outside of main. I would also like to understand what is the difference between the myList.forEach and the for loop below, why the one works and the other one doesn't.

2
  • 2
    possible duplicate of Does Java 8 Support Closures? Commented Aug 6, 2015 at 9:30
  • Also, since you're running Java 8, you may want to look into doing myList.stream().forEach. See java.util.Stream. Commented Mar 29, 2017 at 18:59

3 Answers 3

4

Try with:

final Integer[] sum = new Integer[1];
sum[0] = 0;

myList.forEach(i -> {
    sum[0] = sum[0] + i;
}); 

Since lambda is actually a syntactic sugar for initializing an anonymous class (and overriding a method).

It's the same as if you have written:

final Integer[] sum = new Integer[1];
sum[0] = 0;

myList.forEach(new Consumer() {
    public void accept(Integer element) {
        sum[0] = sum[0] + element;
    }
});

The variable that comes from outer scope and that you use within inner scope must be final (in this example sum). That is simply because Java does not support closures. Therefore, outer variable must be marked as final. Since Integer itself is immutable (if you declare it final, you cannot change it anymore), you have to use a wrapper object or an array (as I did).

You can find more useful info here:

Sign up to request clarification or add additional context in comments.

3 Comments

This doesn't really answer the question.
it works, but what is the reason behind it? Could you provide a detailed explanation of the behaviour? Thanks
@MickMnemonic that's why I changed it. Thank you for pointing that out.
3

Not exactly the answer you are looking for, but in most scenarios you won't need to modify that inside the lambda. This is because it's not idiomatic for lambdas to be state-changing in a proper functional style.

What you can do to achieve your result is use any of the higher-level functions provided to mask the "accumulator", and then assign:

sum = myList.stream().mapToInt(x->x).sum();

Comments

0

A lambda is basically an anonymous class. You can only access final local variables from anonymous class.

What you need is a wrapper class that can modify its content. For a quick hack, you can use AtomicInteger in this case:

AtomicLong sum = new AtomicLong(0);
myList.forEach(i -> {
  sum.addAndGet(i); // does not matter getAndAdd or addAndGet
});

sum.get(); // to get the value

Another way is, if you are using Intellij IDEA, the IDE can suggest you to transform the variable into final one element array (as in darijan's answer).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.