【问题标题】:What does final do in Java? - Hard VersionJava 中的 final 是做什么的? - 硬版
【发布时间】:2012-06-01 03:27:17
【问题描述】:

我从小就知道final 是一个关键字,当它应用于变量时,不能将变量重新分配给其他东西。 “如果一个变量是最终的,那么它就是一个常数”总结了很多,虽然我不喜欢这个定义,但这可能是记住这个概念的好方法。我只是更愿意说you cannot change the value of the variable(无论“价值”是什么意思)。

我的生活很快乐,但有一天我更深入地了解了method local inner classes...

在方法中定义的内部类不能访问在方法本身中定义的变量。为什么?因为虽然类存在于堆中并且它可能在方法完成后仍然存在(类的有效引用可能被传递并存储在其他地方),但这些变量存在于堆栈中,并且当方法返回时它们会消失。我们不希望有一个内部类试图访问一个以后不再存在的变量,因为那样世界就会结束。

完美。这说得通。优秀!然后:除非您将这些变量声明为 final .....然后您的类可以访问它们并且编译器不会将您送入地狱...

WHY???我的意思是,这是什么魔法? final 到底做了什么,为什么我不得不等待谈论方法本地内部类来解决这个问题?假设final变量不管定义在哪里都存储在Heap中,除了让方法局部内部类快乐的概念,还有其他的应用吗?

【问题讨论】:

标签: java inner-classes final


【解决方案1】:

答案要简单得多。

Java 开发人员(开发 java 的人)认为,如果您可以访问内部类中的非 final 变量会造成混淆,因此他们添加了一个要求,即该变量应该是 final 以避免混淆。

我不确定 java 是如何实现这一点的,但我猜他们只是在创建内部类时复制变量。也可以使用非 final 变量轻松完成一些事情,但 java 开发人员认为这会令人困惑。

【讨论】:

    【解决方案2】:

    在您所指的情况下,您可能会陷入奇怪的情况。在方法中创建内部类时,该内部类的范围可以超过方法调用的生命周期。那么,如果你允许内部类引用一个只在方法本身中定义的变量,那么当方法完成时会发生什么?

    例如:

    public void doSomethingLater()
    {
      int value = 13;
    
      Thread t = new Thread(new Runnable() { 
        @Override public void run() {
          System.out.println("The value is " + value);
        }
      });
      t.start();
    
      value = 25;
    }
    

    假设这是允许的 java 代码。该程序在运行时会打印什么? 13? 25?为了实现内部类对方法局部变量的访问,Java 设计者决定采用简单的路线,即在内部类创建时复制变量的值。为了让 Java 程序员清楚地知道该值已被复制,他们强制您使用最终引用,以便您的方法无法在将来的某个时间点修改该变量(这可能会使期望新值会影响的开发人员感到困惑)内部类)。

    实际上,这就是幕后发生的事情:

    public void doSomethingLater()
    {
      int value = 13;
    
      Thread t = new Thread(new Runnable() { 
        private final _innerValue = value;
        @Override public void run() {
          System.out.println("The value is " + _innerValue);
        }
      });
      t.start();
    
      value = 25;
    }
    

    因此,可以看到后面对 value 的修改不会影响到内部类。

    更新:

    好吧,猜猜@pgras 打败了我。无论如何,这是该答案中包含的引用示例。

    【讨论】:

      【解决方案3】:

      引自“The Final word on the final keyword”:

      如果我们对局部类的实现方式有所了解,这个限制的原因 [局部类只能引用声明为 final 的局部变量和参数。] 变得显而易见。匿名局部类可以使用局部变量,因为编译器会自动为该类提供一个私有实例字段来保存该类使用的每个局部变量的副本。编译器还会为每个构造函数添加隐藏参数,以初始化这些自动创建的私有字段。因此,本地类实际上并不访问局部变量,而只是访问它们自己的私有副本。唯一可以正常工作的方法是,如果局部变量被声明为 final,以保证它们不会改变。有了这个保证,本地类就可以确保其变量的内部副本准确地反映了实际的本地变量。

      【讨论】:

        【解决方案4】:

        其实是重复的:Cannot refer to a non-final variable inside an inner class defined in a different method

        看到这个答案:https://stackoverflow.com/a/1299889/277683

        您似乎了解堆栈等,所以我会简短。总而言之,当您引用final 时,编译器可以安全地确定其值在编译时,并为内部类使用相同的东西。

        【讨论】:

          【解决方案5】:

          在 Java 中,final 的字面意思是“如果一个变量是最终的,它就是一个常量”,但这并不意味着很多人认为它的意思。

          对于原语,它确实确实意味着。但是对于对象,final 的所有含义是您无法更改变量引用的对象实例。但是,您完全允许随意更改该对象。

          这就是为什么方法本地内部类需要声明它引用的变量final。它们被声明为 final 保证了方法不能改变这些变量引用的对象,这是方法内部类正常运行所必需的保证。

          当您谈论堆和堆栈时,您会混淆变量和对象。引用对象的本地方法变量 存在于堆栈中,但它们引用的对象 存在于堆中。因此,即使方法超出范围,内部类也可以继续引用这些对象,因为对象实例存在于方法范围之外。

          【讨论】:

            猜你喜欢
            • 2010-11-22
            • 2014-01-13
            • 2011-01-16
            • 2010-11-13
            • 2010-12-08
            • 1970-01-01
            • 2014-12-05
            • 2011-12-20
            相关资源
            最近更新 更多