最后的字段和匿名类

时间:2013-09-19 07:09:33

标签: java anonymous-class

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

假设以下代码:

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属性。
  3. 当我搜索解决方案时,ix究竟如何有效?我发现这个answer表示:

      

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

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

    另一方面,有关stack issues

    的解释
      

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

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

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

    但是这种方法应该在没有final声明的情况下工作甚至,因为它只复制所有字段。

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

    答案是什么?

3 个答案:

答案 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中的一个特例(很遗憾)。在这种情况下,他们像对待一样对待。

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