6

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?

coderz
  • 4,847
  • 11
  • 47
  • 70
  • 1
    i cant tell you why exactly it is like that but if you make idx static and move it outside the main method it works fine – XtremeBaumer Nov 29 '16 at 10:03
  • @XtremeBaumer yes, I tried make idx static and move it outside the main method, working fine. But why? Could you help share more details? – coderz Nov 29 '16 at 10:05
  • I'd add that `.forEach` applies to list and not to a stream: `list.forEach(item -> {...` – Tchopane Nov 29 '16 at 10:11
  • 1
    See also http://stackoverflow.com/questions/22777915/java-8-catch-22-with-lambda-expressions-and-effectively-final and http://stackoverflow.com/questions/38374961/solve-no-final-variable-inside-java-8-stream/38375344#38375344 – Tunaki Nov 29 '16 at 10:15
  • I don't think that any of the three alternatives provided by Tunaki is a good duplicate. All three are answering parts of the question, but none of them is addressing the direct comparison of why the code with `int[]` array works, while an almost identical code with `int` does not. – Sergey Kalinichenko Nov 29 '16 at 10:32
  • 1
    Also http://stackoverflow.com/questions/28961578/why-lambda-forces-me-to-use-single-element-array-instead-of-final-object and http://stackoverflow.com/questions/19554515/using-final-1-element-array-for-anonymous-inner-class (though not in the context of a lambda) – Tunaki Nov 29 '16 at 10:53

3 Answers3

11

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.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 2
    Java lacks pointers to local variables in general. It doesn’t matter whether the type of the variable is primitive or not, the local variable can’t be changed. And that’s a good thing, as local variables have a fixed life time and having a pointer to them that could last longer, is exactly what Java prevents. – Holger Nov 29 '16 at 11:00
  • 1
    This is a great answer, helped me understand this too. With that understanding I now add my variable as a single value in an array[] and reference array[0] to manipulate it to get around this error. – Joseph Cozad Mar 20 '21 at 19:14
2

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.

Tim Biegeleisen
  • 502,043
  • 27
  • 286
  • 360
  • 1
    Why do you think, the “value can never change after initialization”? You can simply write `idx=new int[42];` right afterwards. `idx` is only effectively final, because the code doesn’t do that. – Holger Nov 29 '16 at 11:01
1

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

Viet
  • 3,349
  • 1
  • 16
  • 35