匿名类是否捕获了所有最终变量?

时间:2018-11-08 08:02:22

标签: java anonymous-class

我以为我知道答案,但是一个小时左右的搜索后我找不到任何确认。

在此代码中:

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处理,但是我找不到能说一种或另一种方式的任何东西。

它是否依赖于实现?

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

4 个答案:

答案 0 :(得分:12)

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

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

  

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

Anonymous classes are inner classes

它没有指定有关匿名类应捕获哪些变量或应如何实现捕获的任何内容。

我认为可以合理地推断出,实现无需捕获内部类未引用的变量;但这并不表示他们做不到。

答案 1 :(得分:10)

仅捕获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还是有效地是最终的(Java 8+),除非您和您不是更改该值,该副本将不会更改,从而会导致错误,因为您希望更改该值,因为复制是一个隐藏的操作。为了防止程序员感到困惑,他们认为obj1必须是最终的,因此您永远不会对这种行为感到困惑。

答案 2 :(得分:2)

您的说法令我感到好奇和惊讶,为什么编译器会这样做呢?我必须自己检查一下。所以我做了一个简单的例子,像这样

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存储第二个决赛(那个在anon类中不使用)(ASTORE 2)
  • L2-创建带有argumets(ALOAD 0)thisobj1(ALOAD 1)的新test $ 1

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

答案 3 :(得分:0)

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

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