【问题标题】:Clarification about using final for inner classes关于内部类使用 final 的说明
【发布时间】:2021-11-25 05:38:35
【问题描述】:

除了我在网上找到的涉及最终变量和内部类的内容之外,我还想得到一些澄清。具体来说,我的问题是关于为什么全局变量可以在内部类中使用,而不是为什么某些变量需要是最终的。请让我知道您是否可以对我的推理进行澄清/更正。

在以下示例中,我创建了一个内部类,并且我相信在创建 View.OnClickListener 类型的匿名内部类时会复制“i”。创建内部类时,必须将变量设置为最终变量,以作为对用户的说明,而不是真正需要的东西。据我所知,这就像将变量传递给方法一样。这些变量是重复的,因为 Java 是按值传递的语言。该方法完成后,副本将丢失,并且不会保存对原始值的任何更改。让我们坚持使用 int 以保持此示例的简单性。

Button b;
int j = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    b=findViewById(R.id.button);
    int i = 0;
    b.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            i++;
            j++
        }
    });


}

强制 final 是为了保护程序员,因为 i 的值(在这个例子中)实际上永远不会改变?

是不是 j 不需要 final 修饰符,因为创建内部类时不会复制全局变量?这是因为它们已经存在于其中包含的所有类中,所以不会发生重复?

【问题讨论】:

    标签: java android global-variables final anonymous-inner-class


    【解决方案1】:

    它基本上与 java 的构建方式有关。想想这必须如何在幕后工作。在像这样的内部类中,它可以访问包含实例的“this”引用。所以“this”告诉内部类包含实例的数据在内存中的位置,然后是在哪里找到 int 的偏移量。这是一个常数(虽然不完全,但对你来说是透明的,并且基本上看起来像那样)。所以类知道从哪里获取数据并更新它。局部变量使用不同的内存区域(堆栈)并且它是有限的(想想你的好老朋友堆栈溢出。)通常当方法完成并且不再需要该状态时,该数据被清除。但是类可以比方法的生命周期更长,所以如果你在这样的方法中创建了一个内部类,它必须将所有的方法状态绑定到内部类,所以如果需要可以使用它。这被称为闭包,Java 通常不支持的东西(我不记得最新的或计划的版本,因为我知道它至少被讨论过。)Final 告诉编译器变量的值不会改变.所以如果你有一个 int 并且它被设置为 4 那么它总是 4。这意味着内部类不需要知道在哪里可以找到 int,它可以复制它,因为它总是那个值。现在,如果它是一个引用,那么 final 意味着引用不会改变,或者数据在内存中的位置不会改变,所以它可以继续知道去哪里寻找它。现在与上面所有的非静态方法绑定基本上有一个对“this”的最终引用,它隐式存在,所以它可以默认传递给内部类。

    感谢阅读我的 ted 演讲,我相信有人会说“好吧……”,但这是它的高级要点。

    【讨论】:

    • 感谢您的澄清。不知道为什么投反对票。似乎有很多关于为什么某些变量需要是最终的涉及内部类的错误信息,我找不到任何关于全局变量的信息,但你的解释对我来说是有道理的。
    • Java 做了很多工作来尝试向您隐藏此类内容。如果您使用 C++,答案会更清楚。试图隐藏某些担忧的问题之一是,当它最终崩溃时,它似乎真的很混乱和武断,这里就是这种情况。 C# 很像 java 确实添加了闭包(IIRC 已经有好几年了),所以 java 可以做到,如果他们试图更新旧的内部类,那将是一个不平凡的性能或效率成本。
    • 因此,当您将变量 final 用于匿名内部类时,我可以理解您希望它为 final,因此我们无需担心值在两个不同的地方被修改。如果原始调用它的方法的生命周期更短,最终修饰符是否真的使它存在于封闭类中?
    【解决方案2】:

    我将在我的回答中使用这个例子:

    int j = 0;
    protected void method() {
        int i = 0;
        run(() -> {
            i++; // error
            j++; // no error
        });
    }
    

    可访问性级别决定了一切。比如j是一个全局变量,直到对象存在,它才会一直存在。

    lambda 表达式 (() -> {}) 将使用该对象并保留它。

    对于i,变量将在方法执行结束时被删除。比如lambda不是由方法本身运行的,JVM不知道变量的区域。

    没有final,它认为这个变量没有被我的lambda使用,可以删除它来取回内存空间。

    有了它,它认为它应该等到所有子方法完成。所以直到 lambda 中的所有 lambda 都结束。

    【讨论】:

      猜你喜欢
      • 2017-09-28
      • 1970-01-01
      • 2015-05-03
      • 2023-03-13
      • 1970-01-01
      • 1970-01-01
      • 2013-10-25
      • 2015-02-17
      • 1970-01-01
      相关资源
      最近更新 更多