Explain Codes LogoExplain Codes Logo

Why are only final variables accessible in anonymous class?

java
closure-handling-mechanism
void-return-types
class-level-variables
Alex KataevbyAlex Kataev·Nov 25, 2024
TLDR

Anonymous classes in Java demand local variables they access to be final ensuring immutability. This condition is due to anonymous classes making a copy of these variables—marking them final implies value cannot change, thus preventing synchronization concerns. Here's a quick example:

final int number = 42; new Thread(() -> System.out.println("The ultimate answer to life, the universe and everything is: " + number)).start(); // Safe use of 'number' as it's a constant, isn't it Douglas Adams?!

This code certifies number stays thread-safe when the Thread runs and averts possible inconsistencies if it were mutable.

Effectively final and mutable types

Despite not being explicitly marked final, a local variable can still be used within an anonymous class if it is not modified after initialization — labelled "effectively final".Using mutable types, such as AtomicInteger or custom objects can circumvent the final restriction. That's because the reference doesn't change:

AtomicInteger atomicCount = new AtomicInteger(0); new Thread(() -> atomicCount.incrementAndGet()).start(); // Mutates state but the reference is constant. It's like changing your socks, but not your feet!

However, synchronization becomes crucial when operating across multiple threads to maintain system consistency.

Circumventing scope constraints

Consider local inner classes are like good companions of the enclosing scope. When a variable is declared final, it forms an implicit constancy pact ensuring a reliable value for the inner class. Therefore, using class-level variables or mutable wrappers like arrays, we can converse with non-final variables from inside anonymous classes:

final int[] wrapper = {0}; // A mutable wrapper button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { wrapper[0]++; // Altering the final wrapper; quite the wrap artist, wouldn't you agree? } });

For advanced scenarios, decoding the Java Language Specification (JLS) offers a deep understanding of inner class behavior that enables robust coding practices.

Leverage anonymous class invocations

Java supports closures enabling the capturing of local variables, invoked within anonymous classes. Therefore, only effectively final variables can be accessed through this closure-handling mechanism.

int [] counter = {0}; Runnable r = new Runnable() { public void run() { counter[0]++; } }; r.run(); // An immediate invocation with state alteration; faster than your next internet purchase!

Tackling concurrency and stale data

In concurrent scenarios, it's imperative to ensure that anonymous inner classes avoid stale data. By enforcing finality, Java avoids dangling references to local variables ensuring thread-safety:

final int score = 5; new Thread(() -> { assert score == 5; // Score captured at thread creation. Five points to Gryffindor! }).start();

Moreover, the production of separate class files for each anonymous class may clutter your project; something to consider for clean code practices.

Void method return types

Void return types, such as in onClick(), are tackled using class-level variables as storage mechanisms:

class MyActivity extends Activity { private int clickCount = 0; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Button button = /* ... */ button.setOnClickListener(v -> { clickCount++; // Accessing the class-level variable, well at least it clicked! }); } }

Here, the class-level fields' visibility is leveraged to maintain state across multiple method invocations that cannot return values directly.