当从实例方法返回没有引用其封闭类的匿名类时,它具有对此的引用。为什么?

2022-09-03 15:29:32

当从实例方法返回没有引用其封闭类的匿名类时,它具有对 的引用。为什么?this

请考虑以下代码:

package so;

import java.lang.reflect.Field;

public class SOExample {

    private static Object getAnonymousClassFromStaticContext() {
        return new Object() {
        };
    }

    private Object getAnonymousClassFromInstanceContext() {
        return new Object() {
        };
    }

    public static void main(String[] args) throws NoSuchFieldException, SecurityException {

        Object anonymousClassFromStaticContext = getAnonymousClassFromStaticContext();
        Object anonymousClassFromInstanceContext = new SOExample().getAnonymousClassFromInstanceContext();

        Field[] fieldsFromAnonymousClassFromStaticContext = anonymousClassFromStaticContext.getClass().getDeclaredFields();
        Field[] fieldsFromAnonymousClassFromInstanceContext = anonymousClassFromInstanceContext.getClass().getDeclaredFields();

        System.out.println("Number of fields static context: " + fieldsFromAnonymousClassFromStaticContext.length);
        System.out.println("Number of fields instance context: " + fieldsFromAnonymousClassFromInstanceContext.length);
        System.out.println("Field from instance context: " + fieldsFromAnonymousClassFromInstanceContext[0]);

    }

}

这是输出:

Number of fields static context: 0
Number of fields instance context: 1
Field from instance context: final so.SOExample so.SOExample$2.this$0

每个方法虽然看似调用相同的代码,但正在执行不同的操作。在我看来,实例方法返回的是嵌套类,而静态方法返回的是静态嵌套类(作为静态成员,它显然不能引用 )。this

鉴于没有引用封闭类的事实,我看不到这样做的好处。

幕后发生了什么?


答案 1

匿名/内部类背后有一个设计原则:内部类的每个实例都属于外部类的一个实例。

省略对内部类的引用将改变垃圾回收的行为:它的实现方式是,只要内部类处于活动状态,就不能对外部类进行垃圾回收。
这支持了没有外部类的内部类就不能存在的想法。

应用程序可能依赖于此行为,例如,通过创建一个临时文件并在析构函数中删除它。这样,仅当所有内部类都消失时,才会删除该文件。

这也意味着无法更改当前行为,因为更改它可能会破坏现有应用程序。

因此,当您不需要引用时,应始终将内部类标记为静态,因为这可能会导致一些不错的内存泄漏。

编辑:我想说的例子(抱歉糟糕的代码质量):

class Ideone
{
    static Object[] objects = new Object[2];

    public static void main (String[] args) throws java.lang.Exception
    {
        M1();
        M2();
        System.gc();
    }

    static void M1() {
        objects[0] = new Foo().Bar();
    }
    static void M2() {
        objects[1] = new Foo().Baz();
    }
}

class Foo {
    static int i = 0;
    int j = i++;

    public Foo() {
        System.out.println("Constructed: " + j);
    }

    Object Bar() {
        return new Object() {

        };
    }
    static Object Baz() {
        return new Object() {

        };
    }

    protected void finalize() throws Throwable {
        System.out.println("Garbage collected " + j);
    }
}

输出:

建造时间: 0
建成日期: 1
垃圾收集 1

如您所见,第一个 Foo 没有被垃圾回收,因为仍然有一个“内部实例”处于活动状态。要使此行为正常工作,内部类需要一个引用。

当然,它也可以以不同的方式实现。但我想说的是,保留引用是故意做出的设计决策,这样“内部实例”就不会超过其父实例。

顺便说一句:Java语言参考非常隐晦地说明了这一点(对于不访问外部类的内部类没有例外):

类或接口 O 的直接内部类 C 的实例 i 与 O 的实例相关联,称为 i 的紧闭实例。对象的直接封闭实例(如果有)在创建对象时确定 (§15.9.2)。


答案 2

我只想说:它有一个引用,因为它可能需要它。this

想象一下程序的轻微修改:

public class SOExample
{
    private static Object getAnonymousClassFromStaticContext()
    {
        return new Object()
        {
            @Override
            public String toString()
            {
                // ERROR: 
                // "No enclosing instance of the type SOExample is accessible in scope"
                return SOExample.this.toString(); 
            }
        };
    }

    private Object getAnonymousClassFromInstanceContext()
    {
        return new Object()
        {
            @Override
            public String toString()
            {
                // Fine
                return SOExample.this.toString(); 
            }
        };
    }
}

显然,在实例上下文中创建的对象需要对 的引用,因为它必须能够访问封闭实例的方法(或字段,如果存在)。this

在原始示例中,您没有以任何方式访问封闭实例,这一事实并不意味着默认情况下此引用不存在。this

在哪一点上应该做出其他决定?编译器是否应该检查引用是否确实需要,如果不是,则将其丢弃?如果您不需要 ,请创建一个静态内部类(或从静态上下文创建此实例)。对封闭实例的引用只是内部类的实现方式。thisthis


顺便说一句:即使对于都是从同一“上下文”创建的个对象,与 equal 的比较也会返回 false,只要您不相应地在返回的对象中实现自己的 equals 方法


推荐