Java Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

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.

Java bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
4 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Klaus
Klaus
9 months ago

Is this good?

I am currently struggling with Fortran making it hard to declare various intents to the compiler for static checking (e.g. with the absence of typed enums). This “effectively final” sounds like a step backwards to me.

Loredana Crusoveanu
8 months ago
Reply to  Klaus

Hey Klaus,

Sometimes the compiler needs a variable to be final. For example, you can’t access a non-final local variable from an anonymous class.
In Java versions before Java 8, you had to declare a local variable final to be able to access it from an anonymous class. Starting with Java 8, you don’t have to do it anymore, if the variable is effectively final. But if it isn’t, the compiler will raise an error. So you still have the advantages of static analysis, but with more flexibility.

Cheers.

Michael
Michael
8 months ago

Hi,

Question to section 3.1

”’
String test = hello + ” ” + world;
”’

I thought this line will be changed to?

”’new StringBuilder(“hello”).append(” “).append(“world”);
”’

Not to

”’
String var1 = “hello world”;
”’

Loredana Crusoveanu
7 months ago
Reply to  Michael

Hi Michael, The compiler would use the StringBuilder if the variables weren’t final. But in case they are final, the compiler can optimize the code and use the already concatenated String. You can check it yourself by using javap -c to disassemble the .class file. For the example presented in the article, the jdk8 compiler doesn’t use StringBuilder. Here’s the part of the disassembled code: public static void main(java.lang.String[]); Code: 0: ldc #2 // String hello 2: astore_1 3: ldc #3 // String world 5: astore_2 6: ldc #4 // String hello world 8: astore_3 9: getstatic #5 // Field… Read more »

Comments are closed on this article!