【问题标题】:Are all final variables captured by anonymous classes?所有最终变量都被匿名类捕获吗?
【发布时间】:2021-06-08 07:32:49
【问题描述】:

我以为我知道这个问题的答案,但经过一个小时左右的搜索,我找不到任何确认。

在这段代码中:

public class Outer {

    // other code

    private void method1() {
        final SomeObject obj1 = new SomeObject(...);
        final SomeObject obj2 = new SomeObject(...);
        someManager.registerCallback(new SomeCallbackClass() {
            @Override
            public void onEvent() {
                 System.out.println(obj1.getName());
            }
        });
    }
}

假设registerCallback 将它的参数保存在某个地方,这样匿名子类的对象就会存活一段时间。显然,这个对象必须保持对obj1 的引用,这样onEvent 才能在被调用时工作。

但是考虑到该对象不使用obj2,它是否仍然保持对obj2 的引用,以便obj2 在对象存在时不能被垃圾回收?我的印象是all可见final(或实际上是最终的)局部变量和参数被捕获,因此只要对象还活着就不能被GC'ed,但我可以'找不到任何说法。

它是否依赖于实现?

JLS 中有一个部分可以回答这个问题吗?我无法在那里找到答案。

【问题讨论】:

  • 你怎么知道obj2 绑定到callback$x?你在字节码中见过吗?
  • "是否依赖于实现?"从技术上讲,是的。匿名类没有理由捕获obj2,但没有理由不能。
  • 我认为它不会捕获obj2 的一个很好的理由当然是你可以在一个方法中声明多个匿名类:其中一个类可能只引用obj1,而另一个可能只提到obj2。两个类都捕获这两个变量是不明智的。
  • 您可以使用反射或调试器进行检查。

标签: java anonymous-class


【解决方案1】:

语言规范几乎没有说明匿名类应如何从其封闭范围中捕获变量。

我能找到的语言规范中唯一特别相关的部分是JLS Sec 8.1.3

在内部类中使用但未声明的任何局部变量、形式参数或异常参数必须声明为 final 或有效地为 final(第 4.12.4 节),否则在尝试使用时会发生编译时错误。 )

(Anonymous classes are inner classes)

它没有指定匿名类应该捕获哪些变量,或者应该如何实现捕获。

我认为由此推断实现不需要捕获内部类中未引用的变量是合理的;但并不是说他们不能。

【讨论】:

    【解决方案2】:

    仅捕获obj1

    逻辑上,匿名类被实现为普通类,如下所示:

    class Anonymous1 extends SomeCallbackClass {
        private final Outer _outer;
        private final SomeObject obj1;
        Anonymous1(Outer _outer, SomeObject obj1) {
            this._outer = _outer;
            this.obj1 = obj1;
        }
        @Override
        public void onEvent() {
             System.out.println(this.obj1.getName());
        }
    });
    

    请注意,匿名类始终是内部类,因此它将始终保持对外部类的引用,即使它不需要它。我不知道编译器的更高版本是否已经优化了它,但我不这么认为。这是内存泄漏的潜在原因。

    它的用途变成:

    someManager.registerCallback(new Anonymous1(this, obj1));
    

    如您所见,obj1 的参考值是复制的(按值传递)。

    从技术上讲,obj1 没有理由成为 final,无论是声明为 final 还是 实际上是 final (Java 8+),除非它不是并且您更改了值,复制不会改变,导致错误,因为你希望值改变,因为复制是一个隐藏的动作。为了防止程序员混淆,他们决定 obj1 必须是最终的,所以你永远不会对这种行为感到困惑。

    【讨论】:

    • 值得一提的是,自 Java 8 以来,必须明确声明 final 的限制已被取消。 JLS 现在谈论“有效final”变量。
    • 这不仅仅是逻辑上的,它几乎与编译器所做的完全一样(Java 8 和 11),只是字段名称不同 [:-)
    • 注意this的封闭实例无论是否被使用都会被捕获。
    • @CarlosHeuberger 并且类本身的命名不同,使用了 Java 语法不允许的名称(例如 Outer$1)。
    【解决方案3】:

    我对你的陈述感到好奇和惊讶(为什么编译器会做这样的事情???),我不得不自己检查一下。所以我做了这样的简单例子

    public class test {
        private static Object holder;
    
        private void method1() {
            final Object obj1 = new Object();
            final Object obj2 = new Object();
            holder = new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    System.out.println(obj1);
                }
            };
        }
    }
    

    并导致method1的以下字节码

     private method1()V
       L0
        LINENUMBER 8 L0
        NEW java/lang/Object
        DUP
        INVOKESPECIAL java/lang/Object.<init> ()V
        ASTORE 1
       L1
        LINENUMBER 9 L1
        NEW java/lang/Object
        DUP
        INVOKESPECIAL java/lang/Object.<init> ()V
        ASTORE 2
       L2
        LINENUMBER 10 L2
        NEW test$1
        DUP
        ALOAD 0
        ALOAD 1
        INVOKESPECIAL test$1.<init> (Ltest;Ljava/lang/Object;)V
        PUTSTATIC test.holder : Ljava/lang/Object;
    

    这意味着:

    • L0 - 使用 idx 1 (ASTORE 1) 存储第一个最终结果
    • L1 - 使用 idx 2 存储第二个 final(未在 anon 类中使用)(ASTORE 2)
    • L2 - 使用参数 (ALOAD 0) thisobj1 (ALOAD 1) 创建新的 test$1

    所以我不知道,您是如何得出将obj2 传递给匿名类实例的结论,但这是完全错误的。 IDK如果它是编译器依赖的,但至于其他所说的,也不是不可能的。

    【讨论】:

    • 回答您的问题“我是如何得出结论的”...我以为我很久以前在某个地方读过它,但看起来我记错了。我设想一个编译器在第一次开始处理内部类时创建一个隐藏对象,该对象引用外部 this 并引用所有 final 变量和参数。也许我创造了一个心理画面来帮助我理解内部类发生了什么,然后把它与我认为我读到的东西混淆了?我不知道。
    【解决方案4】:

    obj2 将被垃圾回收,因为它没有引用它。只要事件处于活动状态,obj1 就不会被垃圾回收,因为即使您创建了一个匿名类,您也已经创建了对 obj1 的直接引用。

    final 唯一要做的就是你不能重新定义值,它不能保护对象免受垃圾收集器的影响

    【讨论】:

    • 如果您不理解 OP,为什么要在已经由 4 个不同的人回答的情况下发布此问题的答案。
    • OP 的意思是他认为即使obj2 没有在匿名类中使用,它仍然持有对它的引用(顺便说一句,这是错误的)。它与最终或非声明无关。
    • 我根据标题回答,只要未实例化外部类,op post 中的 obj2 就没有外部引用
    • 您没有抓住重点,如果您根据标题回答,恕我直言,这仍然是题外话。
    猜你喜欢
    • 1970-01-01
    • 2011-11-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-25
    相关资源
    最近更新 更多