我之前问过这个问题,但没有得到适当的答案。
如果非最终字段的值可以更改,那么如何在非匿名字段类中使用非最终字段?
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
如果必须在匿名类中使用局部变量,final以使编译器可以在匿名类代码中内联其值,如下所示:
final
之前:
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
后:
public class Access1 { public Access1() {}//end constructor public void f() { Access1$1 access1$1 = new Access1$1(this); }//end method f }//end class Access1
和
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
那么 ,编译器如何内联非final字段的值?
方法调用的局部变量(final内部类必须可以访问)和实例的私有数据成员之间有很大的区别。
内部类可以访问包含的实例,也可以访问该实例的所有成员final。不需要将它们定型,因为(在您的情况下)已通过引用它们Foo.this。因此,在访问您的i成员时,内部类实际上是在访问Foo.this.i,只是如果引用没有它,就可以隐含Foo.this(如this)。
Foo.this
i
Foo.this.i
this
但是匿名类的代码无法以这种方式访问局部变量,因为它们(当然)不是包含类的实例成员。因此,相反,编译器做了一件非常有趣的事情:为每个局部变量创建 匿名 类的实例成员final,并在创建匿名类的实例时,使用局部变量的值初始化这些成员。
让我们看一下它:
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(); } }
编译后,我们得到InnerEx.class和InnerEx$1.class。如果我们反编译InnerEx$1.class,我们将看到:
InnerEx.class
InnerEx$1.class
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 }
请注意,实例成员名为val$localVar,这是为代表调用中的局部变量而创建的实例成员InnerEx#test。
val$localVar
InnerEx#test