java如何实现内部类闭包?
在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 } }); }
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片段中提到的innerclassA$1
的变量var$i
是另一个变量。构造函数A$1(A,int)
的int
参数是i
的当前(也是唯一)值,构造函数将该值保存在字段val$i
中。实际上,保存变量副本的合成变量未命名为i。根据您使用的编译器版本,它是“$i”或“+i”。