java如何实现内部类闭包?

时间:2010-05-10 17:34:30

标签: java closures inner-classes

在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

3 个答案:

答案 0 :(得分: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(),以防止它优化掉大量代码。)


此处类似讨论

method local innerclasses accessing the local variables of the method

答案 1 :(得分:11)

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

构造函数将此值保存在类变量(字段)中,也称为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
    }
}

在情境A中,i的{​​{1}}字段也需要更改,我们假设这是可能的:它需要引用Action对象。

假设在情况B中,Action的这个实例是Garbage-Collected。

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

因此,为了使VM的实现更简单,Java语言设计者已经说过它应该是最终的,这样VM就不需要检查对象是否消失,并保证变量不被修改并且VM或编译器不必在匿名内部类及其实例中继续引用变量的所有用法。

答案 2 :(得分:3)

本地类实例(匿名类)必须维护变量的单独副本,因为它可能会超出函数的范围。为了不在同一范围内混淆两个具有相同名称的可修改变量,该变量被强制为最终变量。

有关详细信息,请参阅Java Final - an enduring mystery