【问题标题】:When an anonymous class with no references to its enclosing class is returned from an instance method, it has a reference to this. Why?当从实例方法返回一个没有对其封闭类的引用的匿名类时,它具有对 this 的引用。为什么?
【发布时间】:2016-04-09 20:46:12
【问题描述】:

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

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

幕后发生了什么?

【问题讨论】:

  • 这就是闭包不这样做的原因。我假设这简化了实现以不优化 this 或外部类(如果实际上不需要)。
  • 你是什么意思 鉴于没有引用封闭类的事实
  • @RobertBain 引用仍然存在,它与您的匿名类非常相似,该类具有初始化为外部实例的 private SOExample foobar 成员 - 即使您没有任何代码,它仍然存在使用它。对外部实例的引用是存在的,因为 Java 规范说就是这样 - 即使没有使用它的代码,也没有省略引用的特殊情况。
  • 所有本地类都会发生这种情况,而不仅仅是匿名类。老实说,我看不出让在非静态内容中创建的本地类被允许访问外部类的其他成员有什么奇怪的(并且使它可能需要引用来保存外部类的特定实例) .在静态上下文中创建的本地类对实例一无所知,因此它们不需要字段来保存此类实例。

标签: java anonymous-class


【解决方案1】:

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

省略对内部类的引用会改变垃圾收集的行为:它的实现方式,只要内部类还活着,就不能对外部类进行垃圾收集。
这支持了没有外部类就不能存在内部类的想法。

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

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

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

编辑: 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);
    }
}

输出:

已建成:0
已建成:1
垃圾收集 1

如您所见,第一个 Foo 没有被垃圾回收,因为仍然有一个“内部实例”活着。为了使这种行为起作用,内部类需要一个引用。

当然,它也可以以不同的方式实现。但我会说,保留引用是故意做出的设计决策,因此“内部实例”不会比它的父对象寿命长。

顺便说一句:The Java language reference 非常隐晦地说明了这一点(不访问外部类的内部类也不例外):

类或接口 O 的直接内部类 C 的实例 i 是 与 O 的一个实例相关联,称为立即封闭 i 的实例。对象的直接封闭实例,如果 any,在创建对象时确定(第 15.9.2 节)。

【讨论】:

  • 我一定遗漏了一些东西,但从我收集到的信息来看,你所说的与提出的问题相同。如果没有对封闭类的引用,那么实例方法肯定会更好地返回一个Object而不引用this,即一个静态嵌套类。
  • 没有。在这种情况下(返回一个静态嵌套类),保持返回的实例(其他地方)不会保持外部类的活动。如果返回内部类将。我将尝试添加一个示例。
  • 添加示例。我希望它有助于澄清事情。
  • +1 表示“当然,它也可以以不同的方式实现。但我要说,保留引用是故意做出的设计决策,因此“内部实例”不会超过其父级。”当有更大的设计模式在起作用时,在特定情况下寻找小的微优化似乎并不高效。
  • 问题是:一旦它在任何时候以这种方式实现,由于向后兼容性,您无法更改它。所以也许,优化实际上可能对 99% 的应用程序有益,但由于 1% 的应用程序会中断,我们就被这种行为所困扰。在我看来,依靠这种行为来编写 Java 代码是一种糟糕的方式。
【解决方案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,则创建一个静态内部类(或从静态上下文创建此实例)。对封闭实例的引用只是内部类的实现方式。


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

【讨论】:

  • equals 的好点子,圣诞假期对我打击很大! :) 已删除。
【解决方案3】:

即使我们看不到任何可见的参考,它仍然存在。请参阅下面的代码。

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$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]

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

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

正如输出 JVM 所建议的那样,在创建匿名/方法本地类时,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();
    }
}

结论:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-03
    • 2023-04-01
    相关资源
    最近更新 更多