Effectively Final and Capturing Variables

The Rule

A lambda can read local variables from the surrounding method only if they are final or effectively final (never changed after assignment). The lambda captures the value, and Java forbids changes so that the copy and the original can never disagree, even when the lambda runs later on another thread.

int count = 0;
Runnable r = () -> System.out.println(count); // OK: count is effectively final

int total = 0;
list.forEach(x -> total += x);   // COMPILE ERROR: local variables referenced from
                                 // a lambda expression must be final or effectively final

What You Can Change

Example

Fields and the contents of objects can change. For counting, prefer a stream (sum()) or an AtomicInteger over the array trick.

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

public class Main {
    static int fieldCounter = 0;   // fields are not captured, so they can change

    public static void main(String[] args) {
        List<Integer> nums = List.of(10, 20, 30);

        nums.forEach(n -> fieldCounter += n);
        System.out.println("field: " + fieldCounter);

        AtomicInteger atomic = new AtomicInteger();
        nums.forEach(atomic::addAndGet);
        System.out.println("atomic: " + atomic.get());

        int best = nums.stream().mapToInt(Integer::intValue).sum();
        System.out.println("stream sum (best): " + best);

        String prefix = "Item-";             // effectively final
        nums.forEach(n -> System.out.print(prefix + n + " "));
        System.out.println();
    }
}

Output

field: 60
atomic: 60
stream sum (best): 60
Item-10 Item-20 Item-30

this Inside a Lambda

Note: Inside a lambda, this means the enclosing object. Inside an anonymous class, this means the anonymous object itself. A lambda does not create a new scope for names, so you cannot declare a parameter with the same name as a local variable.