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

Java 当从实例方法返回的匿名类没有对其封闭类的引用时,它有对该类的引用。为什么?,java,anonymous-class,Java,Anonymous Class,当从实例方法返回一个没有引用其封闭类的匿名类时,它引用了this。为什么? 考虑以下代码: package so; import java.lang.reflect.Field; public class SOExample { private static Object getAnonymousClassFromStaticContext() { return new Object() { }; } private Object

当从实例方法返回一个没有引用其封闭类的匿名类时,它引用了
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

鉴于没有对封闭类的引用,我看不出这有什么好处


幕后发生了什么?

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

省略对内部类的引用将改变垃圾收集的行为:按照其实现方式,只要内部类处于活动状态,外部类就不能被垃圾收集。
这支持了这样一种观点:没有外部类,内部类就不可能存在

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

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

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

编辑: (对糟糕的代码质量表示抱歉):

输出:

构造:0
构造:1
垃圾收集1

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

当然,它也可以以不同的方式实施。但我要说的是,保留引用是一个有目的的设计决策,这样“内部实例”就不会比其父实例更长寿

顺便说一句:这种说法相当隐晦(不访问外部类的内部类也不例外):

类或接口O的直接内部类C的实例i是 与O的实例关联,称为立即封闭 我的例子。对象的直接封闭实例,如果 任何,在创建对象时确定(§15.9.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
,因为它必须能够访问封闭实例的方法(或字段,如果存在的话)

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

在哪一点上应该做出其他决定?编译器是否应该检查
这个
引用是否确实是必需的,如果不是,就把它扔掉?如果您不想要
这个
,那么创建一个静态内部类(或者从静态上下文创建这个实例)。引用封闭实例只是实现内部类的方式



顺便说一句:与
equal
的比较将返回
false
,即使两个对象都是从相同的“上下文”创建的,只要您不在返回的对象中相应地实现自己的
equals
方法,即使我们看不到任何可见的引用,它仍然存在。请参阅下面的代码

package jetty;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.Arrays;

public class SOExample2 {

    private static Object staticField = new Object () { };
    private Object nonStaticField = new Object () { };

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

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

    public static void main(String[] args) throws NoSuchFieldException, SecurityException {
        System.out.println("Started");

        class StaticMethodLocal {

        }

        System.out.println("############## Fields ##############");
        printClassInfo(staticField.getClass());
        printClassInfo(new SOExample2().nonStaticField.getClass());

        System.out.println("############## Methods ##############");
        printClassInfo(getAnonStatic().getClass());
        printClassInfo(new SOExample2().getAnonNonStatic().getClass());

        System.out.println("############## Method Local ##############");
        printClassInfo(new StaticMethodLocal().getClass());
        printClassInfo(new SOExample2().getNonStaticMethodLocal().getClass());
    }

    public static <T>void printClassInfo(Class<T> klass) {
        System.out.println("Class : " + klass);
        String prefix = "\t";

        System.out.println(prefix + "Number fields : " + klass.getDeclaredFields().length);
        if(klass.getDeclaredFields().length > 0) {
            System.out.println(prefix + "fields : " + Arrays.toString(klass.getDeclaredFields()));
        } else {
            System.out.println(prefix + "no fields");
        }
        System.out.println(prefix + "modifiers : " + Modifier.toString(klass.getModifiers()));

        //Constructors
        Constructor<?>[] constructors = klass.getDeclaredConstructors();
        for(Constructor<?> constructor : constructors) {
            System.out.println(prefix + "constructor modifiers : " + Modifier.toString(constructor.getModifiers()));
            System.out.println(prefix + "constructor parameters : " + Arrays.toString(constructor.getParameterTypes()));
        }
        System.out.println("");
    }

    private Object getNonStaticMethodLocal () {
        class NonStaticMethodLocal {
        }
        return new NonStaticMethodLocal();
    }
}
我又添加了两种类型的匿名类作为字段值和两种方法本地类

给出上面的输出,很明显,在非静态上下文中生成的类只有一个构造函数,而这个构造函数有一个封闭类类型的参数

正如输出所建议的那样,JVM在创建匿名/方法本地类时添加了一些额外的代码来保存封闭类实例引用

这也可以在反编译器输出中看到

    // Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   SOExample2.java

//Static field anonynmouse class
class SOExample2$1
{
    SOExample2$1()
    {
    }
}

//Non static field anonynmouse class
class SOExample2$2
{
    final SOExample2 this$0;
    SOExample2$2()
    {
        this$0 = SOExample2.this;
        super();
    }
}

//static method anonynmouse class
class SOExample2$3
{
    SOExample2$3()
    {
    }
}

//Non static method anonynmouse class
class SOExample2$4
{
    final SOExample2 this$0;
    SOExample2$4()
    {
        this$0 = SOExample2.this;
        super();
    }
}

//Static method local class
class SOExample2$1StaticMethodLocal
{
    SOExample2$1StaticMethodLocal()
    {
    }
}

//Non static method local class
class SOExample2$1NonStaticMethodLocal
{
    final SOExample2 this$0;
    SOExample2$1NonStaticMethodLocal()
    {
        this$0 = SOExample2.this;
        super();
    }
}
结论:

Started
############## Fields ##############
Class : class jetty.SOExample2$1
    Number fields : 0
    no fields
    modifiers : 
    constructor modifiers : 
    constructor parameters : []

Class : class jetty.SOExample2$2
    Number fields : 1
    fields : [final jetty.SOExample2 jetty.SOExample2$2.this$0]
    modifiers : 
    constructor modifiers : 
    constructor parameters : [class jetty.SOExample2]

############## Methods ##############
Class : class jetty.SOExample2$3
    Number fields : 0
    no fields
    modifiers : 
    constructor modifiers : 
    constructor parameters : []

Class : class jetty.SOExample2$4
    Number fields : 1
    fields : [final jetty.SOExample2 jetty.SOExample2$4.this$0]
    modifiers : 
    constructor modifiers : 
    constructor parameters : [class jetty.SOExample2]

############## Method Local ##############
Class : class jetty.SOExample2$1StaticMethodLocal
    Number fields : 0
    no fields
    modifiers : 
    constructor modifiers : 
    constructor parameters : []

Class : class jetty.SOExample2$1NonStaticMethodLocal
    Number fields : 1
    fields : [final jetty.SOExample2 jetty.SOExample2$1NonStaticMethodLocal.this$0]
    modifiers : 
    constructor modifiers : 
    constructor parameters : [class jetty.SOExample2]
  • 编译器在生成我们看不到的类文件时会执行一些操作。例如,向类添加
    默认构造函数
    ,或向未显式调用任何
    this()
    自构造函数或
    super()
    构造函数的构造函数添加默认超类构造函数。添加一个封闭类型的引用也是如此,没有什么神奇之处。我们可以很容易地定义这样一个类,编译器只是为了让我们的生活更轻松
  • 这和编写自己的字节码是一样的。因此,绕过编译器本身,我们可以做到这一点,而实际语言却做不到
  • 如果匿名类中没有
    修饰符
    输出,则可以得出结论,它们只是方法本地类(所有类修饰符public、protected、private、abstract、static)在方法中失去了意义。它们只是打着方法本地类的幌子被称为匿名类。

  • 这就是闭包不这样做的原因。我假设这简化了实现,没有优化掉
    this
    或外部类(如果实际不需要的话)。考虑到没有对封闭类的引用,您是什么意思class@RobertBain参考文献仍然存在,这与您的匿名类非常相似,它有一个初始化为外部实例的
    私有SOExample foobar
    成员,即使您没有任何使用它的代码,它仍然存在。对外部实例的引用是存在的,因为Java规范说这是
        // Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
    // Jad home page: http://www.kpdus.com/jad.html
    // Decompiler options: packimports(3) 
    // Source File Name:   SOExample2.java
    
    //Static field anonynmouse class
    class SOExample2$1
    {
        SOExample2$1()
        {
        }
    }
    
    //Non static field anonynmouse class
    class SOExample2$2
    {
        final SOExample2 this$0;
        SOExample2$2()
        {
            this$0 = SOExample2.this;
            super();
        }
    }
    
    //static method anonynmouse class
    class SOExample2$3
    {
        SOExample2$3()
        {
        }
    }
    
    //Non static method anonynmouse class
    class SOExample2$4
    {
        final SOExample2 this$0;
        SOExample2$4()
        {
            this$0 = SOExample2.this;
            super();
        }
    }
    
    //Static method local class
    class SOExample2$1StaticMethodLocal
    {
        SOExample2$1StaticMethodLocal()
        {
        }
    }
    
    //Non static method local class
    class SOExample2$1NonStaticMethodLocal
    {
        final SOExample2 this$0;
        SOExample2$1NonStaticMethodLocal()
        {
            this$0 = SOExample2.this;
            super();
        }
    }