java如何实现内部类闭包?

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

public class A { public void method() { final int i = 0; doStuff(new Action() { public void doAction() { Console.printf(i); // or whatever } }); } } 

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

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

构造函数将此值保存在类变量(字段)中,也称为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中,还需要更改Action的字段i ,让我们假设这是可能的:它需要对Action对象的引用。

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

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

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

局部变量(显然)不在上面的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."":()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()以防止它优化掉大量代码。)


这里类似的讨论

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

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

请参阅Java Final – 一个持久的谜团 ,了解更多细节。