skip to content

Local & Anonymous Classes

Classes declared inside a method: local classes that capture effectively-final locals, and anonymous classes that implement an interface or extend a class inline. Still worth knowing after lambdas, because anonymous classes can hold state and target non-functional types.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

5

What does it mean that a local or anonymous class can only capture effectively-final local variables, and why does Java impose this rule?

level: middleimportance: must knowfreq 68%

answer

  1. Inner object outlives the stack frame
  2. Capture = copy the value into a synthetic field
  3. Reassignment would let copies drift -> forbidden
  4. Effectively final = assigned once, never reassigned
  5. Workarounds: array box, Atomic, enclosing field

basics

~20 s

A local variable used inside a local/anonymous class must never be reassigned after it's set — that's what 'effectively final' means. Java requires this because the class captures a copy of the variable's value, so allowing reassignment would let the two copies drift apart.

solid answer

~50 s

Local and anonymous classes can outlive the method that created them — for example, an anonymous Runnable handed to another thread runs after the method returns and its stack frame is gone. The class therefore can't reference the live stack slot of a local variable; instead the compiler captures the variable's value into a synthetic field of the inner-class instance. If the local were allowed to change after capture, you'd have two independent copies (the method's and the captured one) that could disagree, which is confusing and error-prone. To avoid this, Java requires captured locals to be final or effectively final — assigned exactly once and never reassigned. 'Effectively final' (since Java 8) means you don't have to write the `final` keyword; the compiler infers it if the variable is in fact never reassigned. To capture mutable state, wrap it: use a one-element array, an AtomicReference, or a field of the enclosing object, which IS captured by reference (via the enclosing-instance pointer).

code

java · 16 lines
java
Runnable makeCounterPrinter() {
    // Won't compile if we try: int n = 0; ... n++ then capture n.
    int[] box = {0};                 // reference is effectively final
    return new Runnable() {
        @Override public void run() {
            box[0]++;                // mutate the array's contents, allowed
            System.out.println(box[0]);
        }
    };
}

void bad() {
    int x = 0;
    x = 5;                           // reassigned -> not effectively final
    Runnable r = () -> System.out.println(x); // COMPILE ERROR
}

go deeper

for a junior

Knows captured locals must be effectively final and that you can't reassign them after use in the inner class.

for a middle

Explains value-capture into a synthetic field, why a vanished stack frame forces it, and the array/Atomic/field workarounds.

for a senior

Distinguishes capturing the variable's binding vs its value, contrasts with closures in other languages, and reasons about thread-safety of captured mutable state.

for a principal

Weighs API/concurrency design around captured state, prefers immutable capture for thread-safety, and guides teams away from mutable-box hacks toward cleaner designs (passing state explicitly, atomics).

## Setup: what 'capture' means When a local class or anonymous class refers to a local variable (or method parameter) from the method it's declared in, that is called **capturing** the variable. Example: ```java Runnable makeTask(String name) { int id = 42; return new Runnable() { @Override public void run() { System.out.println(name + " " + id); // captures `name` and `id` } }; } ``` Here the returned `Runnable` may be run much later, after `makeTask` has already returned. By then the method's **stack frame** — the chunk of memory holding `id`, `name`, and other locals — has been destroyed. So the object can't point at those stack slots. ## How Java actually captures The compiler solves this by **copying the value** of each captured local into a hidden (synthetic) field of the generated inner-class instance at the moment the instance is created. The inner object then reads its own copy, which survives on the heap as long as the object does. Primitives are copied by value; object references are copied (the *reference* is copied, so both point at the same heap object). ## The rule: effectively final Because the inner object holds an independent copy, Java forbids the original local from being **reassigned** anywhere in the method. A variable that is assigned once and never reassigned is **effectively final** — it behaves as if you wrote `final`, even though you didn't. Before Java 8 you had to spell out `final`; since Java 8 the compiler infers it, and you only get a compile error if you actually do reassign. Why the restriction? If reassignment were allowed, the method's copy and the captured copy could **drift apart**, and it would be ambiguous which value the inner class 'should' see — the one at capture time or the latest one. Rather than pick a confusing semantics, Java rules it out at compile time. (Some languages like JavaScript capture the variable *binding* and let it mutate; Java deliberately chose value capture for simplicity and thread-safety.) ## What counts as a reassignment ```java int x = 1; x = 2; // reassignment -> x is NOT effectively final Runnable r = () -> System.out.println(x); // compile error ``` Mutating the *object* a final reference points to is fine — it's the *variable* that must not be reassigned: ```java List<String> list = new ArrayList<>(); // effectively final reference Runnable r = () -> list.add("ok"); // OK: we mutate the object, not the variable ``` ## Working around it when you truly need mutable captured state Three idioms: 1. **One-element array** — `int[] box = {0};` then `box[0]++` inside; the *reference* `box` stays effectively final while the contents change. 2. **Atomic types** — `AtomicInteger count = new AtomicInteger();` then `count.incrementAndGet()`; also thread-safe. 3. **A field of the enclosing object** — instance and static fields are *not* captured by value. The inner class reaches them through the captured **enclosing-instance reference** (for an instance field) or directly (for a static field), so writes are visible and allowed. ## Note on fields vs locals The effectively-final rule applies **only to local variables and parameters**, not to fields. That's because a field lives on the heap (inside the enclosing object), and the inner class accesses it live through the enclosing-instance pointer — there is no copying and therefore no drift problem.

  • Why are fields exempt from the effectively-final rule but local variables are not?
    Fields live on the heap inside the enclosing object and are reached live through the captured enclosing-instance reference, so there is no copied snapshot to drift. Locals live on the stack, which disappears when the method returns, so the inner class must capture a copy — hence they must not be reassigned.
  • How can you increment a counter from inside an anonymous Runnable that captures it?
    Wrap it: use a one-element array (`int[] box = {0}; box[0]++`), an `AtomicInteger`, or make it a field of the enclosing class. The variable itself stays effectively final while the contents/object mutate.

saying these in an interview costs you the question

  • Claiming you must write the `final` keyword (effectively final is inferred since Java 8)
  • Saying you can never mutate captured state (you can mutate the object a final reference points to, or use a box/field)
  • Thinking fields must also be effectively final (the rule is only for locals/params)
  • Believing the inner class reads the live stack variable rather than a captured copy

context

open as a page

What is a local class and what is an anonymous class in Java, and how do they differ?

level: juniorimportance: should knowfreq 55%

basics

~20 s

A local class is a named class declared inside a method body. An anonymous class is a one-time, unnamed class you declare and instantiate in a single expression, usually to implement an interface or extend a class on the spot.

open as a page

When you write an anonymous class with `new SomeType() { ... }`, how does the meaning differ depending on whether SomeType is a class or an interface, and what are the limits?

level: middleimportance: should knowfreq 50%

basics

~20 s

If SomeType is a class, the anonymous class extends it (a subclass) and the () calls that class's constructor. If SomeType is an interface, the anonymous class implements it and the () must be empty because interfaces have no constructor. Either way you get exactly one supertype.

open as a page

Inside an anonymous class, what does `this` refer to, and how do you reach the enclosing instance? How does this differ from a lambda?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Inside an anonymous (or local) class, this means that inner object itself, not the enclosing object. To reach the enclosing instance you write Outer.this. A lambda is different: its this is the enclosing instance, because a lambda has no this of its own.

open as a page

Given lambdas, method references, local classes, and anonymous classes, how do you decide which to use, and what are the trade-offs of each?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Use a lambda or method reference when you just need to fill in one method of a functional interface. Use an anonymous class when you must extend a class, implement multiple methods, or keep state. Use a named local class when you need a constructor or reuse it several times in the method.

open as a page