How can non-final fields be used in a anonymous cl

2019-02-28 05:13发布

问题:

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?

回答1:

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.