6

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

假设以下代码:

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

当我搜索解决方案时,究竟是如何工作i的,x我找到了这个答案,上面写着:

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

它如何适用于字段i以及x它们是否是方法的参数?他们在编译时不知道吗?这种方法可以为z.

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

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

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

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

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

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

正确答案是什么?

4

3 回答 3

8

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

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

这是有道理的,因为有许多函数符号。否则,在将按钮actionPerformed传播到另一个功能调用时,按钮突然需要将其参数设为最终参数。

于 2013-09-19T07:27:09.220 回答
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。同样,您应该考虑如果您xrun方法中进行修改会发生什么。(如 Joop 的回答中所述,在 Java 8 中捕获的局部变量“实际上是最终的” - 因此它们的行为就像您已将它们声明为最终的,但没有明确地这样做。)

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

于 2013-09-19T07:19:02.643 回答
-2

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

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

希望这能澄清最后一个问题。

于 2013-09-19T07:18:47.043 回答