【问题标题】:in java, why do closured variables need to be declared final?在java中,为什么需要将闭包变量声明为final?
【发布时间】:2012-05-16 20:23:32
【问题描述】:
final Object o;

List l = new ArrayList(){{
    // closure over o, in lexical scope
    this.add(o);
}};

为什么必须将o 声明为最终结果?为什么其他具有可变变量的 JVM 语言没有这个要求?

【问题讨论】:

    标签: java jvm closures language-design


    【解决方案1】:

    这不是 JVM 深度的,这一切都发生在语法糖级别。原因是通过闭包导出非最终 var 使其容易受到数据竞争问题的影响,并且由于 Java 被设计为“蓝领”语言,因此在原本温顺且安全的本地 var 的行为中发生了如此惊人的变化被认为太“先进”了。

    【讨论】:

    • 恕我直言,这是一个相当奇怪的设计决定,因为类实例的最终引用的 字段 可能随时更改。
    • @DustinGetz 当多个线程争用同一个 var 时会发生这种情况——至少一次读取和一次写入。
    • @Vlad 类是 final 真的与此无关。这些字段通常是已知的并被接受为受数据竞争的约束。
    • @Marko:这正是我的观点。问题只在一个层面上得到解决,给人一种虚假的安全感。 (我已经更正了“final class”->“final ref”。)
    • @Vlad 但是共享字段是线程之间任何可以想象的数据共享场景的基础!如果您没有这个问题,那么该语言将不需要 synchronizedvolatile costructs。
    【解决方案2】:

    不难从逻辑上推断为什么它必须是final

    在Java中,当一个局部变量被捕获到一个匿名类中时,它是按值复制的。这样做的原因是对象可能比当前函数调用存活得更长(例如,它可能被返回等),但局部变量的存活时间与当前函数调用一样长。所以不可能简单地“引用”该变量,因为那时它可能不存在。某些语言(如 Python、Ruby、JavaScript)确实允许您在范围消失后引用变量,方法是在堆中保留对环境的引用或其他东西。但这在 JVM 上很难做到,因为局部变量是分配在函数的栈帧上的,当函数调用完成时栈帧会被销毁。

    现在,由于它是被复制的,所以变量有两个副本(如果有更多的闭包捕获这个变量,则更多)。如果它们是可分配的,那么您可以更改其中一个而不更改另一个。例如,假设:

    Object o;
    
    Object x = new Object(){
        public String toString() {
            return o.toString();
        }
    };
    o = somethingElse;
    System.out.println(x.toString()); // prints the original object, not the re-assigned one
                                      // even though "o" now refers to the re-assigned one
    

    由于范围内只有一个o 变量,因此您会期望它们引用相同的东西。在上面的示例中,分配给o 后,您会期望稍后从对象访问o 以引用新值;但事实并非如此。这对程序员来说是令人惊讶和意想不到的,并且违反了使用相同变量引用相同事物的原则。

    因此,为了避免这种意外,他们要求您不能在任何地方分配它;即它必须是final

    当然,现在您仍然可以从非final 变量初始化final 变量。在闭包内,您仍然可以将final 变量分配给非final 的其他变量。

    Object a; // non-final
    final Object o = a;
    
    Object x = new Object(){
        Object m = o; // non-final
        public String toString() {
            return ,.toString();
        }
    };
    

    但这一切都很好,因为您明确使用不同的变量,所以它的作用并不奇怪。

    【讨论】:

    • "但是这在 JVM 上很难做到,因为局部变量是分配在函数的栈帧上的,当函数调用完成时栈帧会被销毁。"等等,什么?如果是这样的话,java 可能有析构函数。
    • @DustinGetz:我说的是变量。您将其与对象混淆了。对象永远不能是 Java 中的值。值是原语和引用,与析构函数没有任何关系。
    猜你喜欢
    • 2011-08-16
    • 1970-01-01
    • 1970-01-01
    • 2011-12-12
    • 2012-08-31
    • 1970-01-01
    • 1970-01-01
    • 2011-01-29
    相关资源
    最近更新 更多