我对匿名课程和最终字段的解释仍然不满意。有很多问题试图解释明显的问题,但我没有找到所有问题的答案: - )
假设以下代码:
public void method(final int i, int j) {
final int z = 6;
final int x = j;
int k = 5;
new Runnable() {
public void run() {
System.out.print(i);
System.out.print(x);
System.out.print(z);
System.out.print(k);
}
};
}
k
属性,无法编译此代码。z
属性。当我搜索解决方案时,i
和x
究竟如何有效?我发现这个answer表示:
然后,编译器可以将匿名类中的lastPrice和price的使用替换为常量的值(在编译时,当然),并且您将不再有访问不存在的变量的问题
如果字段i
和x
是方法的参数,它如何工作?在编译期间不知道它们?这种方法适用于z
。
另一方面,有关stack issues:
的解释这允许Java编译器在运行时“捕获”变量的值,并将副本存储为内部类中的字段。一旦外部方法终止并且其堆栈框架已被移除,原始变量就会消失,但内部类的私有副本仍然存在于类本身的内存中
我会理解匿名类在创建过程中以某种方式复制了所有必需的内容(字段)。缺少final
有明显的问题,如果某些代码低于匿名类声明会更改该值,则执行使用可能的stale
值。
但是,这可以解决在使用属性范围之外执行匿名类'方法时的问题。
但是这种方法应该在没有final
声明的情况下工作甚至,因为它只复制所有字段。
这两种方法对我来说都是独立的。说到哪 - 它可以解决我的问题 - 我还没找到工作final
方法字段。即使方法完成,它们也不会从堆栈中删除?对我来说似乎是无稽之谈,但它可以解释很多事情: - )
答案是什么?
答案 0 :(得分:8)
由于需要将变量从方法复制到匿名类(如上所述),因此要求复制的变量是最终的语言设计决策。因此,无论是方法还是匿名类中的赋值都不会给出过时的值,代码也会更加一致。
但是!在Java 8中,此要求得到了缓解:如果变量事实上 final,则不再需要final
:在匿名类中“复制”变量后,不允许赋值。
这是有道理的,因为有许多功能符号。否则按钮的actionPerformed
突然需要将其参数设置为最终,将其传播到另一个函数调用。
答案 1 :(得分:7)
在我看来,你在被声明为final
的变量与常量之间感到困惑。
编译器不会用常量替换对局部变量的所有引用 - 但是当构造匿名类的实例时,每个相关变量的当前值将传递给构造函数,并且存储在匿名类中的变量中。对于参数来说,这与任何其他类型的局部变量一样好。
所以这段代码:
public static void method(final int x) {
Runnable r = new Runnable() {
@Override public void run() {
System.out.println(x);
}
};
r.run();
}
大致相当于:
public static void method(final int x) {
Runnable r = new AnonymousRunnable(x);
r.run();
}
private static class AnonymousRunnable implements Runnable {
private final int x;
AnonymousRunnable(int x) {
this.x = x;
}
@Override public void run() {
System.out.println(x);
}
}
我已经将方法和嵌套类都设置为静态,以避免担心是否捕获this
。
当捕获局部变量时,必须为final
以避免可能导致混淆的情况。假设情况并非如此 - 请考虑这个例子:
void method() {
int x = 10;
Runnable r = new Runnable() {
@Override public void run() {
System.out.println(x);
}
};
x = 20;
r.run(); // Should this print 10 or 20?
}
使用匿名类的当前工作方式,但只删除final
限制,它将打印10 ...但开发人员可能希望它打印20.同样,您应该考虑如果您修改会发生什么x
方法中的run
。 (如Joop的回答所述,在Java 8中,捕获的局部变量是“有效的最终” - 因此它们表现得好像你已经宣布它们是最终的,但没有明确地这样做。)
作为一种不同方法的示例,C#以不同的方式处理闭包(对于匿名函数),将局部变量提升为一种匿名类,以便可以进行修改。这是一种更复杂的方法,但更灵活一点。您可能会发现我的article about closures in Java and C#很有用。
答案 2 :(得分:-2)
我认为你因为使用了基类型而感到困惑。如果你考虑参考,它应该更清楚。
您在创建时是正确的,匿名类将所有引用复制到其自己的上下文中。并且允许这样做所有使用的局部变量(和参数只是另一种局部变量)必须是最终的。所以它不是关于价值,而是关于参考。基类型是java中的一个特例(很遗憾)。在这种情况下,他们像对待一样对待。
希望这澄清了最后的问题。