关于java:当从实例方法返回一个没有引用其封闭类的匿名类时,它会引用它。

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的引用。为什么?

考虑以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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]);

    }

}

这是输出:

1
2
3
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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不是垃圾收集的,因为仍然有一个"内部实例"处于活动状态。要使此行为正常工作,内部类需要一个引用。

当然,它也可以以不同的方式实现。但是我要说的是,保持引用是一个故意作出的设计决策,这样"内部实例"就不会比它的父实例寿命长。

顺便说一句:Java语言引用很神秘地说明了这一点(对于不访问外部类的内部类没有例外):

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,因为它可能需要它。

想象一下对程序的一个微小修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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,那么创建一个静态内部类(或从静态上下文创建这个实例)。对封闭实例的引用只是内部类的实现方式。

by the way:The comparison with equalwill return false,even for two objects that are both created from the same"context",if you don't implement your own equalsmethod in the returned objects respondly.>sub>


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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();
    }
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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添加了一些额外的代码来保存封闭类实例引用。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
    // 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();
    }
}

结论:

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