6
  • case 1: it can work when using for-each loop:
    private void m10(String[] arr) {
        for (String s : arr) {
            Supplier<String> supplier = () -> {
                System.out.println(s);
                return null;
            };
            supplier.get();
        }
    }

or

    private void m10(Object[] arr) {
        for (Object s : arr) {
            Supplier<String> supplier = () -> {
                System.out.println(s);
                return null;
            };
            supplier.get();
        }
    }
  • case 2: it will catch compile-time error
    private void m11(String[] arr) {
        for (int i = 0; i < arr.length; i++) {
            Supplier<String> supplier = () -> {
                System.out.println(arr[i]);
                return null;
            };
            supplier.get();
        }
    }

In case 2, I know the variable i is not effectively final because its value changed between loop iterations. But I cannot understand why the lambda can work in case 1.

Boann
  • 48,794
  • 16
  • 117
  • 146
huangjs
  • 459
  • 1
  • 5
  • 13
  • 1
    Would be helpful to see how `s` is scoped: [How is Java's for loop code generated by the compiler](https://stackoverflow.com/a/3433775/5761558) – ernest_k Oct 23 '19 at 07:30
  • 4
    Because `for(final String s : arr)` would be valid too. That’s what *effectively final* means; adding the `final` keyword would not break it. – Holger Oct 23 '19 at 07:42
  • You can think of case 1 as equal to doing `String s = arr[i];` first thing in the loop in case 2. This too will allow you to do `System.out.println(s);` inside the lambda because `s` is effectively final. (And thanks to @Slaw for pointing out that this is a duplicate; there’s a lot more information in the original question.) – Ole V.V. Oct 23 '19 at 09:28

2 Answers2

7

s is never changed (s = ...). So the compiler says "yeah, we could theoretically mark this as final". That is what is meant by effectively final. I.e. you did not mark it final but you could and it would still compile.

In case you are wondering about the enhanced for-loop:

for (String s : arr)

The variable does not live outside of the scope of the for and does not get re-assigned. I.e. it is not:

String s = null;
for (int i = 0; i < arr.length; i++) {
    s = arr[i];
    ...
}

The variable is created inside the loop, so its scope is limited to the loop. It is not re-used but thrown away and re-created each iteration:

for (int i = 0; i < arr.length; i++) {
    String s = arr[i];
    ...
}

Take a close look at the two examples. In the first, you could not write final String s = null;, because we are re-assigning it during the loop s = arr[i];. But in the second example we can, because the variable s is only known within one iteration and then thrown away again. So final String s = arr[i]; is fine.

As a side note, this also explains why you can not use s after the loop. It is unknown and destroyed already, its scope is limited to the loop.

Zabuzard
  • 25,064
  • 8
  • 58
  • 82
4

Because the scope of s is a single iteration, there isn't one s that changes value, but one effectively final s for each loop iteration.

It would be like writing your case 2 in the following way, which does compile

private void m11(String[] arr) {
    for (int i = 0; i < arr.length; i++) {
        String s = arr[i];
        Supplier<String> supplier = () -> {
            System.out.println(s);
            return null;
        };
        supplier.get();
    }
}
Kayaman
  • 72,141
  • 5
  • 83
  • 121