【问题标题】:How can non-final fields be used in a anonymous class class if their value can change?如果非最终字段的值可以更改,如何在匿名类中使用它们?
【发布时间】:2013-10-12 11:46:10
【问题描述】:

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

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

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

【问题讨论】:

  • 编译器不能内联不是编译时常量的字段。你确定你反编译了正确的代码吗?
  • @Joni 我没有内联上面的字段值。我只是比较内联局部变量值和匿名类使用字段的非最终值的方式(我还不知道)。

标签: java field final local-variables anonymous-class


【解决方案1】:

方法调用的局部变量(必须为final 才能被内部类访问)与实例的私有数据成员之间存在很大差异。

内部类可以访问包含的实例,以及该实例的所有成员,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 时为代表局部变量而创建的实例成员。

【讨论】:

  • @KareemMesbah: “所以...我在类 'Foo' 中创建的匿名类可以使用代码 Foo.this.i 不受任何约束地引用私有字段 'i'? " 是的,但其中的Foo.this. 部分是可选的。 iFoo.this.i 是完全相同的东西(如果 i 本身对编译器来说是模棱两可的,你只需要 Foo.this. 部分)。 “考虑到匿名类是在另一个文件中创建的...” 不,不是,匿名类是在 Foo.java 文件中创建的,就在您的 bar 方法中。它实现一个在别处定义的接口,但该类在Foo中定义。
  • @KareemMesbah:我没有在场外关注一些随机的盲链接。如果您指的是JLS 中的某个部分或Java 教程之一,请直接参考。实际引用您所指的内容也是最佳做法。但同样:不,匿名类是在您的 Foo.java 中创建的,在您的 bar 方法中,您在其中说 = new Runnable() { ... };
  • @KareemMesbah:同样,对象实例成员(i 中的 Foo)和方法调用中的局部变量(i,@ 中的局部变量)之间存在很大差异987654351@的foo方法)。
  • @KareemMesbah:本质上。编译器不会内联最终局部变量的值;相反(这更令人惊讶),它在匿名类上创建实例成员。我已经添加到答案中以表明这一点。
  • @KareemMesbah:它可以被设计成那样,但是人们会很困惑,因为我们有两个独立的东西:局部变量和内部类的成员(有效地)同名。改变一个不会改变另一个。想象一下由此产生的错误,尤其是。如果您考虑多个内部类交互......所以他们选择了final,因为它消除了整个类的问题。
猜你喜欢
  • 1970-01-01
  • 2019-05-30
  • 1970-01-01
  • 2023-03-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多