java如何实现内部类闭包?

java如何实现内部类闭包?,java,closures,inner-classes,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 } }); }

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

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

        doStuff(new Action() {
            public void doAction() {
                Console.printf(i);   // or whatever
            }
        });
    }
}
我的问题是这实际上是如何实现的?
i
如何进入匿名内部
doAction
实现,以及为什么它必须是
最终的

局部变量(显然)在不同的方法之间不共享,例如上面的
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
$javap-v“A\$1”#A$1是匿名操作类。
...
最终int val$i;//用于存储i值的字段。
最终A此为$0;//对“封闭”A对象的引用。
A$1(A,int);//已创建匿名类的构造函数
代码:
堆栈=2,局部变量=3,参数大小=3
0:aload_0
1:aload_1
2:putfield#1//该字段为$0:LA;
5:aload_0
6:iload_2
7:putfield#2//字段val$i:i
10:aload_0
11:特别是#3//方法java/lang/Object。“:()V
14:返回
...
公共无效行为();
代码:
堆栈=2,局部变量=1,参数大小=1
0:getstatic#4//字段java/lang/System.out:Ljava/io/PrintStream;
3:aload_0
4:getfield#2//字段val$i:i
7:invokevirtual#5//方法java/io/PrintStream.println:(I)V
10:返回
这实际上表明

  • i
    变量转换为字段
  • 为匿名类创建了一个构造函数,该类接受对
    a
    对象的引用
  • 它随后在
    doAction()
    方法中访问它
(旁注:我必须将变量初始化为
newjava.util.Random().nextInt()
,以防止它优化掉大量代码。)


这里也有类似的讨论

本地类实例(匿名类)必须维护变量的单独副本,因为它可能会超出函数。为了避免在同一范围内混淆两个同名的可修改变量,该变量被强制为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中,
操作的这个实例是垃圾收集的

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


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

这与线程无关。这只是副作用。很好的java disasm,顺便说一句:对编译器有很好的了解。@Pindatjuh,更新了disasm。。。意识到它优化了很多代码,因为编译器意识到
i
始终为0。使用
javap
进行+1反汇编对于这样的问题是一个好主意,更多的人应该经常尝试,以了解Java编译器是如何工作的。变量
i
不会转换为字段。方法
A.method()
的局部变量
i
仍然是局部变量。javap片段中提到的innerclass
A$1
的变量
var$i
是另一个变量。构造函数
A$1(A,int)
int
参数是
i
的当前(也是唯一)值,构造函数将该值保存在字段
val$i
中。实际上,保存变量副本的合成变量未命名为i。根据您使用的编译器版本,它是“$i”或“+i”。