Explain Codes LogoExplain Codes Logo

Difference between final and effectively final

java
lambda
scope
closures
Nikita BarsukovbyNikita Barsukov·Dec 5, 2024
TLDR

In Java, a variable is effectively final if it remains unaltered after its initialization—explicit final declaration isn't mandatory. While final refers to an unchangeable value post-initialization, effectively final implies that the compiler interprets it as such, regardless of a final label. Keep in mind that lambdas and inner classes can only access variables that are final or effectively final.

int fixValue = 10; // Unchanged — effectively final, just like my grandma's homemade recipe new Thread(() -> System.out.println(fixValue)).start(); // Get in the lambda zone!

Here, fixValue, like my dieting plans, remains constant, thus making it effectively final for lambda usage.

Lambda and Inner Classes: Dealing with Scope

When dealing with scope, we're primarily concerned with how visible a variable is within distinct parts of your code. Come to think of it, it's a lot like playing hide and seek! In the arena of lambdas and anonymous classes, local variables in the enclosing scope must be disguised as final or effectively final.

Lambda: The Party Crasher

For closures, which are beloved party crashers in functions, they capture the essence of variables, craving for consistency and an unchangeable state of variable affairs. This is because closures, much like last-minute guests, often outlive their local context.

Assignment: A Game of Catching

Suppose a variable, running away from assignments like I avoid house chores, gets caught — it's now considered ineffectively final.

int elusiveVar = 42; elusiveVar = 24; // Caught! It's not effectively final anymore.

Getting caught in action would bar its use in a lambda or inner class. Staying one step ahead of potential reassignments is akin to obeying the principles of effective finality, keeping the lambda-related compilation menace at bay.

Code of Conduct

It's advisable to bake your lambdas with a generous stuffing of immutability:

  • Go for a spread of immutable data structures.
  • Write your code, thinking that captured variables are deep in a sleep that they can't be woken from.
  • Keep side dishes (side-effects) at a minimum to retain the flavor of functionality.

Rules of Engagement

Let's compare final and effectively final Java variables to the rules of a card game:

Card Rules

Rule for final card (🃏): Once played, you CANNOT TAKE IT BACK. 🚫🔄

Rule for effectively final card (🎴): Officially, nothing is said, but if NO ONE recalls it, it's treated as if it's a final card. 🤫🚫🔄

What does this ensure?

Both cards (🃏 & 🎴) give the same score unless a rule is broken.

In essence,

final: Explicitly stuck in its place 🃏🔒

effectively final: Can theoretically be retracted, but NEVER IS 🎴❓🔒

Breaking the Rule: A Cautionary Tale

Consider a rule-breaking scenario where you move an 🎴 card designated as effectively final after it’s recognized as such. That would be equivalent to triggering a compiler error in Java. The game relies on the card maintaining its position, much like a lambda needs consistency of its captured variables.

Compiler: The Referee of the Game

Final vs Effectively Final: Cards on the Table

Consider a lambda expression as a card player in your game. For the player (read: lambda) to use a card, they need a guarantee that they won't be dealt a hand with a card that's shifted position — either through a final (🃏) decree or effectively final (🎴) practice.

Compiler's Scrutiny

Your Java compiler is like the referee ensuring a fair match. It evaluates every local variable to certify no foul play (no reassignment) after its initial deal. Once convinced of fair play, the compiler flags the variable as effectively final, regardless of a final marking like a well-played trick shot!

The Penalty Round

It's crucial to understand that once a variable is dealt a new card post-initialization, it loses its effectively final badge, making it an invalid move in lambda expressions and anonymous inner classes.

Perfecting the Strategy

By making a habit of not resettling your variables after initialization—even without deploying final—you can dodge needless errors and up the predictability and readability of your code, especially when contending with Java 8 and beyond.

References