java - 如果非最终字段的值可以更改,如何在匿名类类中使用它们?

标签 java field final local-variables anonymous-class

我以前问过这个问题,但没有得到合适的答案。

如果非最终字段的值可以更改,如何在匿名类类中使用它们?

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字段的值呢?

最佳答案

方法调用的局部变量(必须是 final 才能被内部类访问)和实例的私有(private)数据成员之间有很大区别。

内部类可以访问包含的实例,以及该实例的所有成员,final 或不是。它们不需要是最终的,因为它们是通过(在您的情况下)Foo.this 引用的。所以当访问你的i成员时,内部类实际上是在访问Foo.this.i,它只是那个Foo.this(像this) 如果引用在没有它的情况下是明确的,则可以隐含。

但是匿名类的代码不能以这种方式访问​​局部变量,因为它们(当然)不是包含类的实例成员。因此,编译器做了一件非常有趣的事情:它为每个 final 局部变量创建了 anonymous 类的实例成员,并且在创建匿名类的实例时,它使用局部变量的值初始化这些成员。

让我们看它这样做:

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 时代表局部变量而创建的实例成员。

关于java - 如果非最终字段的值可以更改,如何在匿名类类中使用它们?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19333825/

相关文章:

field - 是否可以使用简单的标识符来命名(或标记)FormStack 字段?

css - 为什么我的电子邮件表单字段太高,而且文本居中?

java - 为什么要在 Java 中的方法参数上使用关键字 "final"?

java - 如果在 xml 中手动创建 bean,Spring 注入(inject)将不起作用

java - Gson 不序列化子类中定义的字段

java - 如何将 java.util.Date 对象转换为 Calendar 对象?

perl - 为什么 fields pragma 与 Perl 中的多重继承不兼容?

Java 最终修饰符

java - 在ArrayBlockingQueue中,为什么将final成员字段复制到局部final变量中?

java - 在设备中安装 Android 应用程序