This occurs because of the way that anonymous classes are implemented. You can see this if you make a slight change to the code and then decompile:
final Runnable other = null;
final Runnable example = new Runnable() {
@Override
public void run() {
System.out.println(other);
}
};
i.e. make the anonymous class refer to a different local variable. This now will compile; we can decompile using javap
and see the interface of the anonymous class:
final class Example$1 implements java.lang.Runnable {
final java.lang.Runnable val$other;
Example$1(java.lang.Runnable);
public void run();
}
(Example$1
is the name by which Java internally refers to the anonymous class).
This shows that the compiler has added a constructor to the anonymous class which takes a Runnable
parameter; it also has a field called val$other
. This name of this field should hint that this field is related to the other
local variable.
You can dig into the bytecode further, and see that this parameter is assigned to val$other
:
Example$1(java.lang.Runnable);
Code:
0: aload_0
// This gets the parameter...
1: aload_1
// ...and this assigns it to the field val$other
2: putfield #1 // Field val$other:Ljava/lang/Runnable;
5: aload_0
6: invokespecial #2 // Method java/lang/Object."<init>":()V
9: return
So, what this shows is the way that anonymous classes access the variables from their enclosing scope: they are simply passed the value at construction time.
This should hopefully show why the compiler stops you from writing code such as that in the question: it needs to be able to pass the reference to the Runnable
to the anonymous class in order to construct it. However, the way that Java evaluates the following code:
final Runnable example = new Runnable() { ... }
is to fully evaluate the right-hand side first, and then assign it to the variable on the left-hand side. However, it needs the value of the variable on the right-hand side in order to pass into the generated constructor of Runnable$1
:
final Runnable example = new Example$1(example);
That example
hasn't been previously declared is not a problem, since this code is semantically identical to:
final Runnable example;
example = new Example$1(example);
so the error that you get isn't that the variable cannot be resolved - however, example
hasn't been assigned a value before it is used as an argument to the constructor, hence the compiler error.
It might be argued that this is simply an implementation detail: it shouldn't matter that the argument has to be passed into the constructor, as there is no way that the run()
method can be invoked prior to the assignment.
Actually, that's not true: you can invoke run()
before the assignment, as follows:
final Runnable example = new Runnable() {
Runnable runAndReturn() {
run();
return this;
}
@Override public void run() {
System.out.println(example);
}
}.runAndReturn();
If referring to example
inside the anonymous class were allowed, you would be able to write this. Hence, referring to that variable is disallowed.
Example$1
class pretty much shows why shouldother
befinal
. If it wasn't, theExample$1
could potentially be dealing with an out-of-date copy ofother
, which would be ... strange, to say the least. – Arriaga