Java 8 Lambda variable scope [duplicate]

2020-06-18 09:59发布

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?

3条回答
Melony?
2楼-- · 2020-06-18 10:18

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.

查看更多
Explosion°爆炸
3楼-- · 2020-06-18 10:22

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.

查看更多
甜甜的少女心
4楼-- · 2020-06-18 10:22

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

查看更多
登录 后发表回答