Java如何实现内部类闭包?

2022-09-01 20:57:49

在Java中,匿名内部类可以引用其局部作用域中的变量:

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

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

我的问题是这实际上是如何实现的?如何实现匿名内部,为什么它必须是?idoActionfinal


答案 1

局部变量(显然)不在不同的方法(如及以上方法)之间共享。但是由于它是最终的,在这种情况下不会发生任何“坏”,因此语言仍然允许它。但是,编译器需要对这种情况做一些聪明的事情。让我们来看看产生: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()


类似的讨论在这里

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


答案 2

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

构造函数将此值保存在一个类变量(字段)中,该类变量也称为 ,将在“闭包”内使用。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中,字段也需要更改,让我们假设这是可能的:它需要对对象的引用。iActionAction

假设在情况 B 中,此实例是垃圾回收的。Action

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

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