1. Introduction

One of the most interesting features introduced in Java 8 is effectively final. It allows us to not write the final modifier for variables, fields, and parameters that are effectively treated and used like final ones.

In this tutorial, we'll explore this feature's origin and how it's treated by the compiler compared to the final keyword. Furthermore, we'll explore a solution to use regarding a problematic use-case of effectively final variables.

2. Effectively Final Origin

In simple terms, objects or primitive values are effectively final if we do not change their values after initialization. In the case of objects, if we do not change the reference of an object, then it is effectively final — even if a change occurs in the state of the referenced object.

Prior to its introduction, we could not use a non-final local variable in an anonymous class. We still cannot use variables that have more than one value assigned to them inside anonymous classes, inner classes, and lambda expressions. The introduction of this feature allows us to not have to use the final modifier on variables that are effectively final, saving us a few keystrokes.

Anonymous classes are inner classes and they cannot access non-final or non-effectively-final variables or mutate them in their enclosing scopes as specified by JLS 8.1.3. The same limitation applies to lambda expressions, as having access can potentially produce concurrency issues.

3. Final vs Effectively Final

The simplest way to understand whether a final variable is effectively final is to think whether removing the final keyword would allow the code to compile and run:

@FunctionalInterface
public interface FunctionalInterface {
    void testEffectivelyFinal();
    default void test() {
        int effectivelyFinalInt = 10;
        FunctionalInterface functionalInterface 
            = () -> System.out.println("Value of effectively variable is : " + effectivelyFinalInt);
    }
}

Reassigning a value or mutating the above effectively final variable would make the code invalid regardless of where it occurs.

3.1. Compiler Treatment

JLS 4.12.4 states that if we remove the final modifier from a method parameter or a local variable without introducing compile-time errors, then it's effectively final. Moreover, if we add the final keyword to a variable's declaration in a valid program, then it's effectively final.

The Java compiler doesn't do additional optimization for effectively final variables, unlike it does for final variables.

Let's consider a simple example that declares two final String variables but only uses them for concatenation:

public static void main(String[] args) {
    final String hello = "hello";
    final String world = "world";
    String test = hello + " " + world;
    System.out.println(test);
}

The compiler would change the code executed in the main method above to:

public static void main(String[] var0) {
    String var1 = "hello world";
    System.out.println(var1);
}

On the other hand, if we remove the final modifiers, the variables would be considered effectively final, but the compiler won't remove them since they're only used for concatenation.

4. Atomic Modification

Generally, it's not a good practice to modify variables used in lambda expressions and anonymous classes. We cannot know how these variables are going to be used inside method blocks. Mutating them might lead to unexpected results in multithreading environments.

We already have a tutorial explaining the best practices when using lambda expressions and another that explains common anti-patterns when we modify them. But there's an alternative approach that allows us to modify variables in such cases that achieves thread-safety through atomicity.

The package java.util.concurrent.atomic offers classes such as AtomicReference and AtomicInteger. We can use them to atomically modify variables inside lambda expressions:

public static void main(String[] args) {
    AtomicInteger effectivelyFinalInt = new AtomicInteger(10);
    FunctionalInterface functionalInterface = effectivelyFinalInt::incrementAndGet;
}

5. Conclusion

In this tutorial, we learned about the most notable differences between final and effectively final variables. In addition, we provided a safe alternative that allows us to modify variables inside lambda functions.