Java 最终字段和匿名类

Java 最终字段和匿名类,java,anonymous-class,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);

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

假设以下代码:

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);
        }
    };
}
  • 由于“unfinal”
    k
    属性,无法编译此代码
  • 我知道编译器可以在编译期间用声明的值替换
    z
    属性
  • 当我搜索解决方案时,
    I
    x
    我发现了这样一条:

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

    如果字段
    i
    x
    是该方法的参数,它如何工作?它们在编译时是未知的吗?这种方法适用于
    z

    另一方面,有一种解释是关于:

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

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

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

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

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


    正确答案是什么?

    我想您会感到困惑,因为您使用了基类型。如果你考虑参考资料,它应该会变得更清楚

    创建一个匿名类将所有引用复制到它自己的上下文中,这是正确的。要做到这一点,所有使用的局部变量(参数只是另一种局部变量)必须是最终的。所以这不是关于值,而是关于引用。基类型是java中的一个特例(即sad)。在那种情况下,他们被当作参考人对待


    希望这能澄清最后的问题

    在我看来,您似乎混淆了被声明为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);
        }
    }
    
    我将方法和嵌套类都设置为静态,以避免担心是否捕获了

    捕获局部变量时,必须将其设置为
    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 <代码>会发生什么。(正如Joop的回答中所指出的,在Java8中,捕获的局部变量是“有效的最终变量”——因此它们的行为就像您已经声明它们是最终变量一样,但没有显式地这样做。)


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

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

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


    这是有意义的,因为有许多函数符号。否则,当将按钮的
    操作传播到另一个函数调用时,突然执行的
    操作将需要其参数为final。

    否,这里没有特殊情况。在每种情况下,变量的值都会复制到匿名类中—无论该值是引用还是基元值。我认为在这里区分类和基元类型没有任何好处。我从来没有说过这样的话,请正确阅读我在提供的问题示例中引用的内容。我已经多次阅读了你的答案,但我仍然看不出它有什么帮助。。。尤其是“在这种情况下,他们被当作参考人对待”是错误的。匿名类实例的创建只是将局部变量的值复制到新实例中。这些值是引用值还是原语值(“base”不是标准Java术语的一部分)无关紧要,您关注的焦点是dis