Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java stopped erroring on non-final variables in inner classes (java 8) [duplicate]

Java 7 was saying "Cannot refer to the non-final local variable message defined in an enclosing scope" on following code:

public class Runner {   
    public static void main(String[] args) {

        String message = "Hello world";

        new Runnable() {
            @Override
            public void run() {
                System.out.println(message);
            }
        }.run();
    }
}

Java 8 does not.

Suspect this is about adding functional programming features to Java.

Does it process the code similarly?

like image 667
Suzan Cioc Avatar asked Feb 09 '15 10:02

Suzan Cioc


People also ask

Can we use Non final local variable inside a local inner class?

A local inner class cannot be instantiated from outside the block where it is created in. Till JDK 7, the Local inner class can access only the final local variable of the enclosing block. However, From JDK 8, it is possible to access the non-final local variable of enclosing block in the local inner class.

What is effectively final in Java 8?

starting in Java SE 8, a local class can access local variables and parameters of the enclosing block that are final or effectively final. A variable or parameter whose value is never changed after it is initialized is effectively final.

How can we make an object effectively final in Java?

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.

Why should local variables be final or effectively final?

The local variables that a lambda expression may use are referred to as “effectively final”. An effectively final variable is one whose value doesn't change after it's first assigned. There is no need to explicitly declare such a variable as final, although doing so would not be an error.


3 Answers

Java 8 implicitly makes message final because it is never modified. Try modifying it anywhere in your code and you will get a compilation error (because this removes the implicit final).

This is called effectively final. Quoting From the docs:

However, starting in Java SE 8, a local class can access local variables and parameters of the enclosing block that are final or effectively final. A variable or parameter whose value is never changed after it is initialized is effectively final.

like image 192
Matt Avatar answered Oct 13 '22 11:10

Matt


Java 8 (and Lambdas) introduce the effectively final term: even though you didn't delcare it final with the final keyword, if it is not modified, it is as good as final.

Quoting from Oracle Tutorial: Local Classes:

However, starting in Java SE 8, a local class can access local variables and parameters of the enclosing block that are final or effectively final. A variable or parameter whose value is never changed after it is initialized is effectively final.

Your message is effectively final so it is valid to refer to it from anonymous inner classes and lambdas.

If you change the value of the message, it will not be effectively final anymore:

String message = "Hello world";
new Runnable() {
    @Override
    public void run() {
        System.out.println(message);
    }
}.run();
message = "modified";

And therefore you get the following error (from Eclipse):

Local variable message defined in an enclosing scope must be final or effectively final

Or form javac:

error: local variables referenced from an inner class must be final or effectively final

like image 24
icza Avatar answered Oct 13 '22 11:10

icza


The variable message is effectively final. Quoting from language reference

If a variable is effectively final, adding the final modifier to its declaration will not introduce any compile-time errors.

Hence, because message reference is not changed anywhere within you inner class, the compiler treats it as effectively final.

This would have thrown error:

new Runnable() {
            @Override
            public void run() {
                message = "hey";
                System.out.println(message);
            }
        }.run();

The reason, java7 compiler throws error is because of a spec change for lambdas.

Any local variable, formal parameter, or exception parameter used but not Any local variable, formal parameter, or exception parameter used but not declared in a lambda expression must either be declared final or be effectively final (§4.12.4), or a compile-time error occurs where the use is attempted.

Anonymous Inner classes and lambdas share the same rules.

like image 2
Jatin Avatar answered Oct 13 '22 12:10

Jatin