最后的字段和匿名类

Mar*_*val 6 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);
        }
    };
}
Run Code Online (Sandbox Code Playgroud)
  1. 由于"不完整" k属性,无法编译此代码.
  2. 我知道编译器可以z在编译期间用声明的值替换属性.

当我搜索解决方案时,究竟是如何工作的i,x我发现这个答案说:

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

它如何适用于字段i,x如果它们是方法的参数?在编译期间不知道它们?这种方法可以发挥作用z.

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

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

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

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

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

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

什么是正确的答案?

Joo*_*gen 8

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

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

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


Jon*_*eet 7

在我看来,你在被声明的变量final和它是一个常量之间感到困惑.

编译器不会用常量替换对局部变量的所有引用 - 但是当构造匿名类的实例时,每个相关变量的当前值将传递给构造函数,并存储在匿名类中的变量中.这对于参数和任何其他类型的局部变量一样好.

所以这段代码:

public static void method(final int x) {
    Runnable r = new Runnable() {
        @Override public void run() {
            System.out.println(x);
        }
    };
    r.run();
}
Run Code Online (Sandbox Code Playgroud)

大致相当于:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经将方法和嵌套类都设置为静态,以避免担心是否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?
}
Run Code Online (Sandbox Code Playgroud)

使用匿名类的当前工作方式,但只是删除final限制,它将打印10 ...但开发人员可能希望它打印20.同样,您应该考虑如果您xrun方法中修改会发生什么.(如Joop的回答所述,在Java 8中,捕获的局部变量是"有效的最终" - 所以它们表现得好像你已经宣布它们是最终的,但没有明确地这样做.)

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