2013-10-12 20 views
1

我之前問過這個問題,但我沒有得到一個合適的答案。如果非final字段的值可能更改,如何在匿名類類中使用它?

非最終字段如何在匿名類類中使用,如果它們的值可以更改?

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使編譯器內聯匿名類代碼一樣,裏面它們的值:

前:

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 

然後如何編譯內嵌非最終字段的值?

+0

編譯器不能內聯不是編譯時間常量的字段。你確定你反編譯了正確的代碼嗎? – Joni

+0

@Joni我沒有內聯上面的字段值。我只是比較內聯的局部變量值和匿名類使用字段的非最終值(我還不知道)的方式。 – Kareem

回答

5

方法調用的局部變量(必須是內部類可訪問的final)與實例的私有數據成員之間存在很大差異。

內部類可以訪問包含的實例,也可以訪問該實例的所有成員final。他們沒有必要是最終的,因爲他們被引用(在你的情況下)Foo.this。因此,當訪問您的i會員時,內部類正在訪問Foo.this.i,只是如果沒有它的話,引用是明確的,那麼Foo.this(如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.classInnerEx$1.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局部變量的實例成員。

+0

因此,在我的情況下,我在類'Foo'內創建的匿名類可以在不使用代碼Foo.this.i的情況下使用任何約束來引用** private **字段'i'?考慮到匿名類在另一個文件中是分開的,並且它的引用在封閉類內部使用(就像我在我的問題中寫的代碼那樣),封閉類的私有成員不應該不可訪問匿名類(它成爲一個單獨的文件,並引用它的封閉類)?對不起,我知道這有點令人困惑:D – Kareem

+1

@KareemMesbah:*「所以...我在類'Foo'中創建的匿名類可以使用代碼Foo引用專用字段'i'而不受任何約束。 this.i?「*是的,但是'Foo.this.'部分是可選的。 'i'和'Foo.this.i'完全相同(如果''i''本身對編譯器不明確,則只需要'Foo.this.'部分)。 *「考慮到匿名類是在另一個文件中創建的......」*否,不是,匿名類是在'Foo.java'文件中創建的,就在你的'bar'方法中。它*實現了*在其他地方定義的接口,但該類是在'Foo'中定義的。 –

+0

您可以查看http://goo.gl/xHqksx上的**最終局部變量**部分嗎?這就是我的意思*「匿名類在另一個文件中分隔」*。這不正確嗎? – Kareem