1

以前にこの質問をしましたが、適切な回答が得られませんでした。

値が変わる可能性がある場合、非最終フィールドを匿名クラス クラスでどのように使用できますか?

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

それでは、コンパイラはどのようにして非 final フィールドの値をインライン化できるのでしょうか?

4

1 に答える 1

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.classますInnerEx$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

于 2013-10-12T11:53:48.520 に答える