Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 Lambda variable scope [duplicate]

I have 2 code samples:

int[] idx = { 0 };
List<String> list = new ArrayList<String>();
list.add("abc");
list.add("def");
list.add("ghi");
list.stream().forEach(item -> {
    System.out.println(idx[0] + ": " + item);
    idx[0]++;
});

Working properly.

While this code has compile error:

int idx = 0;
List<String> list = new ArrayList<String>();
list.add("abc");
list.add("def");
list.add("ghi");
list.stream().forEach(item -> {
    System.out.println(idx + ": " + item);
    idx++;
});

Saying:

Local variable idx defined in an enclosing scope must be final or effectively final.

The only difference is idx int or int array.

What's the root cause?

like image 338
coderz Avatar asked Nov 29 '16 09:11

coderz


People also ask

What is scope of lambda expression in Java 8?

1 Scope of a Lambda Expression. The body of a lambda expression has the same scope as a nested block. The same rules for name conflicts and shadowing apply. It is illegal to declare a parameter or a local variable in the lambda that has the same name as a local variable.

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.

Why should lambda variables be final?

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.

Can lambda function have multiple methods?

Since a lambda function can only provide the implementation for 1 method it is mandatory for the functional interface to have ONLY one abstract method.


2 Answers

The root cause is that JVM lacks mechanisms of constructing references to local variables, which is what is needed to perform idx++ when idx is an int or some immutable type (e.g. String). Since you try to mutate idx, simply capturing its value would not be sufficient; Java would need to capture a reference, and then modify the value through it.

Java does not have this problem when you use an array, because arrays are reference objects. Java can capture array reference that never changes, and use that non-changing reference to mutate the object. Array itself provides the necessary level of indirection, because Java arrays are mutable.

I tried make idx static and move it outside the main method, working fine. But why?

Because in this situation there is no need for the lambda to capture a reference to a local variable of primitive type. A reference to the static variable is readily available, so there is no problem with capturing it.

Similarly, the code would work if you make idx a member variable, and use your lambda inside an instance method. This would let lambda modify idx field through this object, which could be freely captured.

like image 149
Sergey Kalinichenko Avatar answered Oct 23 '22 12:10

Sergey Kalinichenko


I have a partial explanation for your observations. The initialized array in your Java 8 code is considered as effectively final, because its value does not change after initialization. This is why the int[] idx = { 0 }; version of your code is getting through. So I would expect if you make int idx effectively final, then it would also pass. One way to do this would be to formally make this variable final by declaring it so, i.e. final int idx = 0.

like image 24
Tim Biegeleisen Avatar answered Oct 23 '22 14:10

Tim Biegeleisen