Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java - changing the value of a final variable from within a lambda

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.

like image 361
AlexGuevara Avatar asked Aug 06 '15 09:08

AlexGuevara


People also ask

Can we change the lambda expression variable data?

Yes, you can modify local variables from inside lambdas (in the way shown by the other answers), but you should not do it.

How do you use the final variable in lambda?

The local variables that a lambda expression may use are referred to as “effectively final”. An effectively final variable is one whose value doesn't change after it's first assigned. There is no need to explicitly declare such a variable as final, although doing so would not be an error.

Why can't we manipulate a local variable inside a lambda?

A lambda expression can't define any new scope as an anonymous inner class does, so we can't declare a local variable with the same which is already declared in the enclosing scope of a lambda expression. Inside lambda expression, we can't assign any value to some local variable declared outside the lambda expression.

Why do local variables used in lambdas have to be final?

The basic reason this won't compile is that the lambda is capturing the value of start, meaning making a copy of it. Forcing the variable to be final avoids giving the impression that incrementing start inside the lambda could actually modify the start method parameter.


2 Answers

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:

  • Why are only final variables accessible in anonymous class?
  • Cannot refer to a non-final variable inside an inner class defined in a different method
like image 160
darijan Avatar answered Oct 03 '22 20:10

darijan


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();
like image 23
Diego Martinoia Avatar answered Oct 03 '22 20:10

Diego Martinoia