Backend Development 6 min read

Why Java Anonymous Inner Classes Require Final or Effectively Final Variables and How Kotlin Handles Them Differently

The article explains why Java anonymous inner classes can only access final or effectively final variables, shows the compiler‑generated bytecode that enforces this rule, and contrasts it with Kotlin's approach of wrapping primitives to allow modification inside such classes.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Why Java Anonymous Inner Classes Require Final or Effectively Final Variables and How Kotlin Handles Them Differently

When developers work with multiple programming languages, they often encounter subtle differences in language features; this article focuses on the behavior of anonymous inner classes in Java compared to Kotlin.

Phenomenon Description

Before Java 8, using a non‑final variable inside an anonymous inner class caused a compilation error: “Cannot refer to a non‑final variable arg inside an inner class defined in a different method.” After Java 8, the error disappears, but attempts to modify the variable still produce an error like “Variable 'num' is accessed from within inner class, need to be final or effectively final.” Screenshots illustrate both cases.

Despite the relaxed warning, the variable remains immutable within the inner class.

Reason Analysis

Compiling the code with javac generates several .class files; the TestInnerClass$1.class file represents the compiled anonymous inner class. Decompiling it reveals that the compiler copies primitive variables and passes object references, effectively treating the inner class as a regular class.

class TestInnerClass$1 extends InnerClass {
    TestInnerClass$1(TestInnerClass var1, int var2, DataBean var3) {
        super(var1);
        this.this$0 = var1;
        this.val$num = var2;
        this.val$bean = var3;
    }
    void doSomething() {
        super.doSomething();
        System.out.println("num = " + this.val$num);
        System.out.println("bean name is: " + this.val$bean.name);
    }
}

The compiler copies primitive values, so modifying them inside the inner class would cause inconsistency with the outer scope, which is why they must be final or effectively final.

Scenario Comparison

Kotlin does not impose this restriction because its compiler wraps primitive values in reference objects, turning value passing into reference passing. The decompiled Kotlin code shows the use of IntRef to hold the primitive, allowing modification inside the anonymous class.

public final void useNestedClass(@NotNull final TestNestedClass.DataBean bean) {
    final IntRef num = new IntRef(); //---1
    num.element = 1; //---2
    String var3 = "before action, num = " + num.element;
    System.out.println(var3);
    TestNestedClass.NestedClass nestedClass = new TestNestedClass.NestedClass() {
        public void doSomething() {
            num.element = 678; //---3
            bean.setName("xyz");
            String var1 = "num = " + num.element;
            System.out.println(var1);
            var1 = "bean name is: " + bean.getName();
            System.out.println(var1);
        }
    };
    nestedClass.doSomething();
    String var4 = "after action, num = " + num.element; //---4
    System.out.println(var4);
}

When no primitive is captured, Kotlin generates straightforward code without wrappers, confirming that the wrapper is only introduced for captured primitives.

public final void useNestedClass(@NotNull TestNestedClass.DataBean bean) {
    int num = 1;
    String var3 = "before action, num = " + num;
    System.out.println(var3);
    int num = 678;
    var3 = "after action, num = " + num;
    System.out.println(var3);
}

This demonstrates why Kotlin can modify captured primitive values while Java cannot.

In summary, Java enforces final/effectively‑final constraints on variables accessed by anonymous inner classes by copying values at compile time, whereas Kotlin circumvents the limitation by wrapping primitives, allowing mutable access inside the inner class.

JavacompilationKotlinAnonymous Inner ClassEffective FinalFinal Variable
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.