Background:
Before Java 8, to use variables from the outer scope in an inner class or an anonymous class, these variables had to be declared as final. In Java 8, this requirement was relaxed: now a variable can be non-final as long as it is not changed (effectively final).
Problem:
Lambda expressions and inner classes use closures over variables from the outer block. However, if such variables change, it creates confusion and incorrect behavior — it becomes unclear what value to use.
Solution:
The compiler allows a variable to be used in an inner class or lambda only if it is effectively final — meaning it was never modified after initialization, even if not explicitly declared as final.
Code example:
public void demo() { int x = 10; Runnable r = () -> System.out.println(x); // x is effectively final r.run(); }
If you try to change x:
public void demo() { int x = 10; x = 20; // now x is not effectively final Runnable r = () -> System.out.println(x); // Compilation error }
Key features:
Can mutable objects be used if the reference itself is effectively final?
Yes, if the reference does not change, but the object referred to can change — this is allowed. For instance,
List<String> list = new ArrayList<>(); list.add("A"); Runnable r = () -> System.out.println(list.get(0)); // OK list = new ArrayList<>(); // Compilation error if done
Can a variable be declared final and still modify the object's contents?
Yes. Final refers to the reference, not the content of the object. Changing the state of the object through the reference is allowed. For example,
final List<Integer> nums = new ArrayList<>(); nums.add(5); // OK nums = new ArrayList<>(); // Error
Can method parameters be used in lambdas?
Yes, if they are also effectively final — that is, not changed within the method after initialization.
In a method, variables are used that are accidentally overwritten after the lambda is created, resulting in the program not compiling and wasting time figuring out the cause.
Pros:
Cons:
The developer uses an incrementable value through AtomicInteger (or another holder object), which holds a reference rather than a value, ensuring correct lambda behavior even when needing to modify the counter within the lambda expression.
Pros:
Cons: