问题
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?
回答1:
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.
回答2:
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
回答3:
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.
回答4:
Yes, it sort of is.
Basically they realised that the compiler already has to decide by analysing the code when a local variable is "effectively final", that is, its value is never changed.
So the semantics haven't changed: while there's no need to explicitly declare the variable final
any more, you still have to make sure it is never reassigned.
来源:https://stackoverflow.com/questions/28408109/java-stopped-erroring-on-non-final-variables-in-inner-classes-java-8