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

When an anonymous class with no references to its enclosing class is returned from an instance method, it has a reference to this. Why?

当一个匿名 class 没有引用其封闭的 class 从实例方法返回时,它有一个对 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.this[=13=]

每个方法,虽然看似调用相同的代码,但实际上在做不同的事情。在我看来,实例方​​法返回一个嵌套的class,而静态方法返回一个静态嵌套的class(作为静态成员,它显然不能引用this).

鉴于没有引用封闭的 class,我看不出这样做有什么好处。

幕后发生了什么?

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

遗漏对内部 class 的引用会改变垃圾回收的行为:它的实现方式,只要内部 class 就不能对外部 class 进行垃圾回收还活着。
这支持了没有外部 class.

就不能存在内部 class 的想法

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

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

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

编辑: Example of what I am trying to say(抱歉代码质量太差):

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

输出:

Constructed: 0
Constructed: 1
Garbage collected 1

如您所见,第一个 Foo 不是 垃圾收集器,因为还有一个 "inner instance" 活着。要使此行为起作用,内部 class 需要引用。

当然,也可以以不同的方式实施。但我要说的是,保留引用是有意做出的设计决定,因此 "inner instance" 不会比其父项长寿。

顺便说一句:The Java language reference 非常神秘地说明了这一点(内部 classes 也不例外,它们不访问外部 class):

An instance i of a direct inner class C of a class or interface O is associated with an instance of O, known as the immediately enclosing instance of i. The immediately enclosing instance of an object, if any, is determined when the object is created (§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 的引用,因为它必须能够访问封闭实例的方法(或字段,如果存在)。

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

在什么时候应该做出不同的决定?编译器是否应该检查 this 引用是否确实需要,如果不需要则将其丢弃?如果您不想要 this,则创建静态内部 class(或从静态上下文创建此实例)。引用封闭实例只是内部 classes 的实现方式。


顺便说一下:与 equal 的比较会 return false 即使 两个 个对象是 两个都是从同一个"context"创建的,只要你不在相应的returned对象中实现你自己的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();
    }
}

输出:

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

Class : class jetty.SOExample2
    Number fields : 1
    fields : [final jetty.SOExample2 jetty.SOExample2.this[=11=]]
    modifiers : 
    constructor modifiers : 
    constructor parameters : [class jetty.SOExample2]

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

Class : class jetty.SOExample2
    Number fields : 1
    fields : [final jetty.SOExample2 jetty.SOExample2.this[=11=]]
    modifiers : 
    constructor modifiers : 
    constructor parameters : [class jetty.SOExample2]

############## Method Local ##############
Class : class jetty.SOExample2StaticMethodLocal
    Number fields : 0
    no fields
    modifiers : 
    constructor modifiers : 
    constructor parameters : []

Class : class jetty.SOExample2NonStaticMethodLocal
    Number fields : 1
    fields : [final jetty.SOExample2 jetty.SOExample2NonStaticMethodLocal.this[=11=]]
    modifiers : 
    constructor modifiers : 
    constructor parameters : [class jetty.SOExample2]

我添加了另外两种类型的匿名 classes 作为字段值和两种本地方法 classes。

上面的输出很明显,在非静态上下文中生成的 classes 只有一个构造函数,并且这个构造函数有一个包含 class 类型的参数。

正如输出所建议的那样,JVM 在创建 anonymous/method 本地 class.

时添加了一些额外的代码来保存包含 class 实例引用

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

    // 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
{
    SOExample2()
    {
    }
}

//Non static field anonynmouse class
class SOExample2
{
    final SOExample2 this[=12=];
    SOExample2()
    {
        this[=12=] = SOExample2.this;
        super();
    }
}

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

//Non static method anonynmouse class
class SOExample2
{
    final SOExample2 this[=12=];
    SOExample2()
    {
        this[=12=] = SOExample2.this;
        super();
    }
}

//Static method local class
class SOExample2StaticMethodLocal
{
    SOExample2StaticMethodLocal()
    {
    }
}

//Non static method local class
class SOExample2NonStaticMethodLocal
{
    final SOExample2 this[=12=];
    SOExample2NonStaticMethodLocal()
    {
        this[=12=] = SOExample2.this;
        super();
    }
}

结论:

  1. 编译器在生成我们看不到的 class 文件时会做一些事情。例如,将 default constructor 添加到 class 或将默认超级 class 构造函数 super() 调用添加到未显式调用任何 this() 自构造函数的构造函数或 super() 构造函数。添加封闭类型的引用也是如此,那里没有魔法。我们可以很容易地定义这样一个 class,编译器通过为我们做这件事来让我们的生活更轻松。
  2. 这类似于编写您自己的字节码。因此绕过编译器本身,我们可以做到实际语言做不到的事情。
  3. 鉴于匿名 classes 中没有 modifier 输出,可以得出结论,它们只是方法本地 classes(所有 class修饰符 public,protected,private,abstract,static) 在方法内部失去意义。他们只是被称为匿名 class 假借他们是本地方法 classes.