24

在 Java 中,匿名内部类可以引用其本地范围内的变量:

public class A {
    public void method() {
        final int i = 0;

        doStuff(new Action() {
            public void doAction() {
                Console.printf(i);   // or whatever
            }
        });
    }
}

我的问题是这实际上是如何实现的?如何i获得匿名内部doAction实现,为什么必须这样做final

4

3 回答 3

16

method()局部变量(显然)不在不同的方法之间共享,例如doAction()上述方法。但由于它是最终的,在这种情况下不会发生任何“坏事”,所以语言仍然允许它。然而,编译器需要对这种情况做一些聪明的事情。让我们看看javac产生了什么:

$ javap -v "A\$1"           # A$1 is the anonymous Action-class.
...
final int val$i;    // A field to store the i-value in.

final A this$0;     // A reference to the "enclosing" A-object.

A$1(A, int);  // created constructor of the anonymous class
  Code:
   Stack=2, Locals=3, Args_size=3
   0: aload_0
   1: aload_1
   2: putfield #1; //Field this$0:LA;
   5: aload_0
   6: iload_2
   7: putfield #2; //Field val$i:I
   10: aload_0
   11: invokespecial #3; //Method java/lang/Object."<init>":()V
   14: return
   ...
public void doAction();
  Code:
   Stack=2, Locals=1, Args_size=1
   0: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   3: aload_0
   4: getfield #2; //Field val$i:I
   7: invokevirtual #5; //Method java/io/PrintStream.println:(I)V
   10: return

这实际上表明它

  • i变量变成一个字段,
  • 为匿名类创建了一个构造函数,它接受对A对象的引用
  • 它后来在该doAction()方法中访问。

(附注:我必须初始化变量以new java.util.Random().nextInt()防止它优化掉大量代码。)


类似的讨论在这里

方法局部内部类访问方法的局部变量

于 2010-05-10T17:37:04.667 回答
12

编译器会自动为你的匿名内部类生成一个构造函数,并将你的局部变量传递给这个构造函数。

构造函数将此值保存在一个类变量(字段)中,也称为i,它将在“闭包”中使用。

为什么它必须是最终的?好吧,让我们探讨一下它不是的情况:

public class A {
    public void method() {
        int i = 0; // note: this is WRONG code

        doStuff(new Action() {
            public void doAction() {
                Console.printf(i);   // or whatever
            }
        });

        i = 4; // A
        // B
        i = 5; // C
    }
}

i在情况 A的字段Action也需要更改,让我们假设这是可能的:它需要对Action对象的引用。

假设在情况 B 中,这个实例Action是垃圾收集的。

现在在情况 C:它需要一个实例Action来更新它的类变量,但值是 GCed。它需要“知道”它是 GCed,但这很困难。

所以为了简化 VM 的实现,Java 语言设计者说它应该是 final 的,这样 VM 就不需要检查对象是否消失,并保证变量没有被修改,并且VM 或编译器不必保留匿名内部类及其实例中变量的所有用法的引用。

于 2010-05-10T17:55:29.047 回答
3

本地类实例(匿名类)必须维护变量的单独副本,因为它可能会超出函数的寿命。为了不让两个同名的可修改变量在同一个作用域内产生混淆,强制变量为final。

有关更多详细信息,请参阅Java Final - 一个不朽的谜团

于 2010-05-10T17:40:52.330 回答