java如何实现内部类闭包?

the*_*oop 24 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
            }
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

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

aio*_*obe 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
Run Code Online (Sandbox Code Playgroud)

这实际上表明了它

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

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


这里类似的讨论

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

  • 它与线程没什么关系.这只是一个副作用.不错的java disasm,btw:对编译器有一些很好的了解. (2认同)
  • +1使用`javap`进行反汇编是一个很好的想法,这样的问题,更多人应该更频繁地尝试来学习Java编译器的工作原理. (2认同)

Pin*_*juh 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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

因此,为了保持虚拟机更简单的实现,Java语言的设计者说,这应该是最终使得虚拟机并不需要一种方法来检查对象是否已经过去,保证变量不被修改,并且VM或编译器不必在匿名内部类及其实例中保留变量的所有用法的引用.