在Java中,匿名内部类可以引用其本地范围中的变量:
public class A {
public void method() {
final int i = 0;
doStuff(new Action() {
public void doAction() {
Console.printf(i); // or whatever
}
});
}
}
我的问题是这是如何实际实现的? i
如何进入匿名内部doAction
实施,为什么必须final
?
答案 0 :(得分: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
这实际上表明它
i
变量转换为字段A
对象doAction()
方法中访问。(旁注:我必须将变量初始化为new java.util.Random().nextInt()
,以防止它优化掉大量代码。)
此处类似讨论
method local innerclasses accessing the local variables of the method
答案 1 :(得分:11)
编译器自动为您的匿名内部类生成构造函数,并将您的局部变量传递给此构造函数。
构造函数将此值保存在类变量(字段)中,也称为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中,i
的{{1}}字段也需要更改,我们假设这是可能的:它需要引用Action
对象。
假设在情况B中,Action
的这个实例是Garbage-Collected。
现在处于情境C:它需要Action
的实例来更新它的类变量,但值是GCed。它需要“知道”它的GCed,但这很难。
因此,为了使VM的实现更简单,Java语言设计者已经说过它应该是最终的,这样VM就不需要检查对象是否消失,并保证变量不被修改并且VM或编译器不必在匿名内部类及其实例中继续引用变量的所有用法。
答案 2 :(得分:3)
本地类实例(匿名类)必须维护变量的单独副本,因为它可能会超出函数的范围。为了不在同一范围内混淆两个具有相同名称的可修改变量,该变量被强制为最终变量。
有关详细信息,请参阅Java Final - an enduring mystery。