This question already has an answer here:
Closed 3 years ago.
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?
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
.
A variable or parameter whose value is never changed after it is initialized is effectively final.
In case 1:
int[] idx
don't change, if you replace idx[0]++;
to idx = {1};
will compile error
In case 2:
if you remove idx++
; it will compile fine