最后的字段和匿名类

关于匿名课和最终字段的解释我仍然不满意。 有很多问题试图解释明显的问题,但我没有找到所有问题的答案:-)

假设以下代码:

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); } }; } 
  1. 由于“不完整”的k属性,无法编译此代码。
  2. 我知道编译器可以在编译期间用声明的值替换z属性。

当我搜索解决方案时, ix确切i ,我发现这个答案说:

然后,编译器可以将匿名类中的lastPrice和price的使用替换为常量的值(在编译时,当然),并且您将不再有访问不存在的变量的问题

如果它们是方法的参数,它如何适用于字段ix ? 在编译期间不知道它们? 这种方法适用于z

另一方面,有关于堆栈问题的解释:

这允许Java编译器在运行时“捕获”变量的值,并将副本存储为内部类中的字段。 一旦外部方法终止并且其堆栈框架已被移除,原始变量就会消失,但内部类的私有副本仍然存在于类的自身内存中

我会理解匿名类在创建过程中以某种方式复制了所有必需的内容(字段)。 缺少final有明显的问题,如果匿名类声明下面的某些代码会改变值,则执行使用可能的stale值。

但是,这可以解决当匿名类’方法在使用的属性范围之外执行时的问题。

但这种方法即使没有final声明也应该有效,因为它只复制所有字段。

这两种方法对我来说都是独立的。 说到哪 – 它可以解决我的问题 – 我还没有找到工作final方法领域。 即使方法完成,它们也不会从堆栈中删除? 对我来说似乎胡说八道,但它会解释很多事情:-)

什么是正确的答案?

在我看来,你在一个被声明为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.同样,你应该考虑如果你在run方法中修改x将会发生什么。 (如Joop的回答所述,在Java 8中,捕获的局部变量“实际上是最终的” – 所以它们就像你已经宣布它们是最终的一样,但没有明确地这样做。)

作为一种不同方法的示例,C#以不同的方式处理闭包(对于匿名函数),将局部变量提升为一种匿名类,以便可以对它们进行修改。 这是一种更复杂的方法,但更灵活一点。 您可能会发现我关于Java和C#中的闭包的文章很有用。

由于需要将变量从方法复制到匿名类(如上所述),因此要求复制的变量是最终的是语言设计决策 。 因此,无论是方法还是匿名类中的赋值都不会给出过时的值,代码会更加一致。

但! 在Java 8中,这个要求得到了缓解:如果变量事实上是最终的,则不再需要final:在匿名类中“复制”变量之后,不允许赋值。

由于许多函数符号,这是有道理的。 否则,按钮的actionPerformed突然需要将其参数设置为final,然后将其传播到另一个函数调用。

我认为你因为使用了基类型而感到困惑。 如果你考虑参考,它应该更清楚。

您在创建时是正确的,匿名类将所有引用复制到其自己的上下文中。 并且允许这样做所有使用的局部变量(和参数只是另一种局部变量)必须是最终的。 所以它不是关于价值,而是关于参考。 基类型是java中的一个特例(很难过)。 在这种情况下,他们对待参考。

希望这澄清了最后的问题。