final变量都是匿名捕获的类吗?

Are all final variables captured by anonymous classes?

我以为我知道这个问题的答案,但搜索了大约一个小时后我找不到任何确认。

在此代码中:

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 visible final (or effectively final) local variables and parameters are captured 因此只要对象还活着就不能进行 GC ,但我找不到任何说明这种情况的内容。

是否依赖于实现?

JLS 中是否有部分回答了这个问题?我在那里找不到答案。

只有 obj1 被捕获。

逻辑上,匿名 class 被实现为正常的 class 类似这样的东西:

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());
    }
});

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

它的使用变成:

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

可以看到,obj1的引用值是copied(传值)。

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

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

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
    DUP
    ALOAD 0
    ALOAD 1
    INVOKESPECIAL test.<init> (Ltest;Ljava/lang/Object;)V
    PUTSTATIC test.holder : Ljava/lang/Object;

这意味着:

  • L0 - 存储第一个 idx 为 1 (ASTORE 1) 的词尾
  • L1 - 用 idx 2 存储第二个决赛(那个在匿名 class 中没有使用)(ASTORE 2)
  • L2 - 使用参数 (ALOAD 0) thisobj1 (ALOAD 1)
  • 创建新的测试 $1

所以我不知道,你是怎么得出 obj2 被传递给匿名 class 实例的结论的,但这完全是错误的。 IDK如果是compiler dependent,但是至于其他人说的,也不是不可能。

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

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

Any local variable, formal parameter, or exception parameter used but not declared in an inner class must either be declared final or be effectively final (§4.12.4), or a compile-time error occurs where the use is attempted.)

(Anonymous classes are inner classes)

它没有具体说明匿名 class 应该捕获哪些变量,或者应该如何实现捕获。

我认为可以从中推断出实现不需要捕获内部 class 中未引用的变量是合理的;但它并没有说他们不能。

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

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