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?
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.
Yes, you can modify local variables from inside lambdas (in the way shown by the other answers), but you should not do it.
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.
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.
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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With