Explain Codes LogoExplain Codes Logo

Cannot refer to a non-final variable inside an inner class defined in a different method

java
closures
effectively-final
encapsulation
Anton ShumikhinbyAnton Shumikhin·Jan 31, 2025
TLDR

To fix the situation, use either final or an effectively final variable. Those final dudes can't change, while the effectively final ones aren't modified after assignment, they're like crouching tiger, hidden final:

void someMethod() { int num = 1; // It's "effectively final", it doesn't say it but it is, like a secret agent. new Thread(() -> System.out.println(num)).start(); // I'm talking to you, num! }

On the other hand, for a variable that has a chameleon tendency and needs to change, wrap it in a one-element array or a wrapper class marked as final:

void someMethod() { final int[] counter = {0}; // This little array got a VIP pass to the final club new Thread(() -> { counter[0]++; System.out.println(counter[0]); // Say hello to the array, it won't bite, it's mutable. }).start(); }

Exploring Closures in Java

In Java, they don't really go all gung-ho on "true closures" like some languages. In those, variables get VIP treatment, they have their own security known as state preservation. But hey, Java has got its own charm, it follows different strategies like effectively final and encapsulation.

Strategies with mutable variables

When dealing with mutable variables, you got two options:

  1. Encapsulate them in a final structure like a Matryoshka doll or use member variables.
  2. Get creative with a static inner class or instance methods.

Using member variables

Use the private club of member variables:

class Outer { private int myModifiableInt; // I'm a member, I got access privileges! void someMethod() { myModifiableInt = 0; new Thread(() -> { myModifiableInt++; System.out.println(myModifiableInt); // I'm talking to the privileged guy }).start(); } }

Leveraging static inner class

Put static inner class to good use, they can be quite handy:

class Outer { private static class Holder { static int myModifiableInt = 0; // I'm static, I'm always here! } void someMethod() { new Thread(() -> { Holder.myModifiableInt++; System.out.println(Holder.myModifiableInt); // Shouting out to static class }).start(); } }

These solutions are all about bringing the A-game when dealing with threads and concurrency.

Transitioning modification to instance methods

When the twists and turns of variable modification get more complex than a soap opera, it’s better to move it into instance methods. It's like outsourcing your stress:

class Outer { private int myCounter; // I'm private, intruders stay away! void incrementCounter() { myCounter++; // Let me handle the dirty work. } void someMethod() { new Thread(this::incrementCounter).start(); // I'll do the incrementing, you chill! } }

Exploring design patterns

Design patterns are like the Avengers of programming, they've got solutions to complex problems:

  • Deploy the Command Pattern to tuck actions with state under its wings.
  • Start the Observer Pattern starship to react to state changes.
  • Gear up for advanced control flows and state management with reactive programming using streams.

The rationale behind it all

Java has a principle—it values the lifecycle of objects. An inner class instance can live longer than the method that created it. So, the final variables provide stability—kind of like an anchor in the stormy asynchronous contexts.

Java's pass-by-value principle

Java, the gentleman it is, always passes by value. Changes in the method don't affect the original values. However, you have workarounds:

  • You can use collections or custom objects that act as a bridge.

Further study

Wanna dive deeper? Here are some awesome resources: