Captured Local Variables in Java Lambdas

A Java lambda may capture a local variable only when that variable is `final` or effectively final. The captured value remains available after the enclosing method has returned because the lambda object retains the needed state.

A lambda can reference variables declared in its enclosing method. Such variables are called captured local variables because their values become part of the lambda's retained state.

The captured local variable must be explicitly declared `final`, or it must be effectively final. Effectively final means the variable is assigned once and is never reassigned afterward; the compiler can treat it as final for capture purposes.

This restriction exists because local variables normally have method-local lifetime and storage semantics, while a lambda can outlive the method invocation. Java therefore captures the value rather than providing unrestricted access to a mutable local variable.

Object references can be captured even when the referenced object itself is mutable. The restriction applies to reassignment of the local reference, not to mutations performed through that reference.

Captured locals should be distinguished from instance fields. A lambda can access and modify instance fields through `this`, whereas a captured local variable cannot be reassigned from inside the lambda.

Key points

Interview questions

What does it mean for a local variable to be effectively final?

A local variable is effectively final when it is initialized and never reassigned afterward. It does not need an explicit `final` modifier. Such a variable can be referenced from a lambda just like an explicitly final local. Assigning a new value to that variable later would prevent it from being captured.

Why does Java require captured local variables to be final or effectively final?

Local variables belong to a method invocation, while a lambda object can continue to exist after that invocation ends. Java therefore captures the value of an eligible local instead of exposing a mutable local-variable slot. Requiring final or effectively final semantics avoids ambiguity about subsequent assignments. It also gives lambda capture predictable value semantics.

Can a lambda modify an object referenced by a captured local variable?

Yes, provided the local reference itself remains final or effectively final. For example, a captured `List` reference can be used to call `add`, because that changes the list rather than assigning a new list to the local variable. Reassigning the reference, such as `list = new ArrayList<>()`, would violate effective finality. Thus finality applies to the local reference, not necessarily to the object's contents.

What happens if a captured local variable is reassigned after the lambda is declared?

The code fails to compile because the local is no longer effectively final. Java analyzes assignments to the variable rather than allowing the lambda to observe later changes to a local slot. The usual fix is to avoid reassignment, introduce a new variable, or use an appropriate mutable holder when shared mutable state is genuinely required. A mutable holder should be used deliberately because it changes the design from simple value capture to shared mutable state.

How does lambda capture differ from accessing an instance field?

A captured local variable must be final or effectively final because it is a local value whose lifetime is tied to a method invocation. An instance field belongs to an object and remains accessible as long as the relevant object is available. A lambda can therefore read or modify an instance field, subject to normal access and concurrency rules. This distinction is important when deciding whether state should be immutable captured data or object state.