skip to content

What restriction does Java place on lambdas and inner classes regarding shadowing local variables, and why?

level: seniorimportance: should knowfreq 35%

answer

  1. Lambda shares enclosing scope -> cannot shadow locals
  2. Reusing enclosing local name = compile error
  3. Captured locals must be effectively final
  4. Inner-class field CAN shadow outer field
  5. Outer.this.field reaches the enclosing field

basics

~20 s

A lambda cannot declare a parameter or local with the same name as a variable already in scope in the enclosing method; that is a compile error. Anonymous and local inner classes also capture effectively-final locals and cannot redeclare them.

solid answer

~50 s

A lambda shares the scope of the method it is written in, so it cannot shadow an enclosing local variable or parameter: declaring a lambda parameter or local with a name already in scope is a compile error, exactly as redeclaring a local would be. This differs from a parameter shadowing a field, which is allowed, because lambdas treat enclosing locals as part of the same scope rather than a separate one. Lambdas (and anonymous/local classes) also capture enclosing locals only if those locals are effectively final, meaning never reassigned after initialization, so the captured value is stable. An inner class can still shadow an enclosing field using its own field of the same name and reach the outer one with Outer.this.field, but it cannot reshadow enclosing method locals. These rules keep captured names unambiguous and the captured state consistent.

code

java · 6 lines
java
void demo() {
    int n = 3;
    // Runnable bad = () -> { int n = 1; }; // ERROR: n already in scope
    Runnable ok = () -> System.out.println(n); // OK: n effectively final, captured
    // n = 4; // would break 'effectively final' and the capture above
}

go deeper

for a junior

Aware that lambdas can use surrounding variables; may not know the shadowing restriction.

for a middle

Knows captured locals must be effectively final and that reusing an enclosing local name in a lambda is an error.

for a senior

Explains why lambdas share the enclosing scope (no local shadowing) while inner-class fields can shadow, using Outer.this to disambiguate.

for a principal

Reasons about closure capture semantics, the value-copy rationale for effectively-final, and design implications for concurrency and readability.

## Background: capture and scope A **lambda expression** is a compact anonymous function, e.g. `x -> x + 1`. An **anonymous inner class** is a class defined inline. Both can **capture** variables from the surrounding method — use them inside even though they were declared outside. ## Rule 1: lambdas do not get a fresh scope for local names Unlike a method (whose parameters may shadow fields), a **lambda body shares the lexical scope of the enclosing method**. Therefore a lambda parameter or a local declared inside it **cannot reuse a name already in scope**; doing so is a **compile error**, the same as redeclaring a local: ```java int x = 5; Runnable r = () -> { int x = 1; }; // COMPILE ERROR: x already in scope Function<Integer,Integer> f = x -> x; // COMPILE ERROR if x is in scope ``` This is deliberate: because lambdas were designed to read like inline code in the same scope, allowing them to silently shadow enclosing locals would be confusing and error-prone. (Anonymous classes, by contrast, *do* introduce a class scope, so their members can shadow enclosing names — but their bodies still cannot redeclare an enclosing method local in an overlapping block.) ## Rule 2: captured locals must be effectively final A lambda or inner class may only capture a local that is **effectively final** — assigned once and never modified afterward (it need not carry the `final` keyword since Java 8). The reason is consistency: captured locals are copied by value into the closure, so permitting reassignment would create two diverging copies and a confusing data race in concurrent use. Fields are not subject to this rule (the closure captures `this`, so it sees live field state). ```java int base = 10; // effectively final: never reassigned Runnable r = () -> System.out.println(base); // OK // base = 11; // would make 'base' NOT effectively final -> compile error ``` ## Inner classes and field shadowing An inner class can declare its own field that **shadows** an enclosing class's field. The enclosing field is then reached via the qualified `Outer.this.field`: ```java class Outer { int v = 1; class Inner { int v = 2; int sum() { return v + Outer.this.v; } // 2 + 1 } } ``` So shadowing of **fields** across the inner/outer boundary is allowed and resolved with `Outer.this`; shadowing of enclosing **method locals** is not. ## Summary - Lambda params/locals **cannot** shadow enclosing method locals/params -> compile error. - Captured locals must be **effectively final**. - Inner-class **fields** may shadow enclosing fields; reach them with `Outer.this.field`. These rules trade a little flexibility for unambiguous names and stable captured state.

  • Why can a method parameter shadow a field but a lambda parameter cannot shadow an enclosing local?
    A method introduces a new scope distinct from the class's field scope, so shadowing is allowed. A lambda shares the enclosing method's scope, so reusing an in-scope local name is a redeclaration error.
  • What does effectively final mean and why is it required for capture?
    It means the local is never reassigned after initialization. It is required so the captured value is stable, avoiding divergent copies between the closure and the enclosing method.

saying these in an interview costs you the question

  • Saying lambda parameters may freely shadow enclosing locals
  • Claiming captured locals can be reassigned after capture
  • Thinking effectively final requires the final keyword
  • Confusing Outer.this.field (inner class) with plain this.field

context