ProgrammingJava Developer

What is effectively final in Java, how is it related to lambda expressions and inner classes, and what nuances should be known?

Pass interviews with Hintsage AI assistant

Answer.

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:

  • The Java compiler determines whether a variable is effectively final.
  • Violating this rule leads to compilation errors.
  • Not only lambdas but also anonymous inner classes are subject to this rule.

Trick questions.

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.

Common mistakes and anti-patterns

  • Unintentionally altering variables used in a lambda or inner class, leading to compilation errors.
  • Confusion between final references and the state of objects referred to by those references.
  • Trying to circumvent the limitation using arrays or holder objects can lead to subtle bugs.

Real-life example

Negative case

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:

  • Code behaves as expected if variables are used correctly.

Cons:

  • Difficulties in error analysis if effectively final is violated implicitly.

Positive case

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:

  • No compilation errors.
  • Clearly shows that the value is changing.

Cons:

  • Easy to make mistakes with multithreaded access if thread-safe objects are not used.