I asked this question before but I didn't get an appropriate answer.
How can non-final fields be used in a anonymous class class if their value can change?
class Foo{
private int i;
void bar(){
i = 10
Runnable runnable = new Runnable (){
public void run (){
System.out.println(i); //works fine
}//end method run
}//end Runnable
}//end method bar
}//end class Foo
If the local variables which are used inside an anonymous class must be final
to enable the compiler inlining their values inside the anonymous class code like that:
Before:
public class Access1 {
public void f() {
final int i = 3;
Runnable runnable = new Runnable() {
public void run() {
System.out.println(i);
}//end method run
};//end anonymous class
}//end method f
}//end class Access1
After:
public class Access1 {
public Access1() {}//end constructor
public void f() {
Access1$1 access1$1 = new Access1$1(this);
}//end method f
}//end class Access1
And
class Access1$1 implements Runnable {
Access1$1(Access1 access1) {
this$0 = access1;
}//end constructor
public void run() {
System.out.println(3);
}//end method run
private final Access1 this$0;
}//end class Access1$1
Then how can the compiler inline a value of a non-final field?
There's a big difference between a method call's local variable (which must be final
to be accessible to an inner class), and an instance's private data members.
The inner class has access to the containing instance, and to all of the members of that instance, final
or not. There's no need for them to be final, because they're referenced through (in your case) Foo.this
. So when accessing your i
member, the inner class is really accessing Foo.this.i
, it's just that Foo.this
(like this
) can be implied if a reference is unambiguous without it.
But the anonymous class's code can't access local variables that way, because they aren't (of course) instance members of the containing class. So instead, the compiler does a very funny thing: It creates an instance member of the anonymous class for each final
local variable, and when creating the instance of the anonymous class, it initializes those members with the values of the local variables.
Let's watch it do that:
public class InnerEx {
public static final void main(String[] args) {
new InnerEx().test("hi");
}
private void test(String arg) {
final String localVar = arg;
Runnable r = new Runnable() {
public void run() {
System.out.println(localVar);
}
};
r.run();
}
}
When compiled, we get InnerEx.class
and InnerEx$1.class
. If we decompile InnerEx$1.class
, we see this:
class InnerEx$1 implements java.lang.Runnable {
final java.lang.String val$localVar;
final InnerEx this$0;
InnerEx$1(InnerEx, java.lang.String);
Code:
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:LInnerEx;
5: aload_0
6: aload_2
7: putfield #2 // Field val$localVar:Ljava/lang/String;
10: aload_0
11: invokespecial #3 // Method java/lang/Object."<init>":()V
14: return
public void run();
Code:
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #2 // Field val$localVar:Ljava/lang/String;
7: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: return
}
Note the instance member called val$localVar
, which is the instance member created to stand in for the local variable in the call to InnerEx#test
.