When an anonymous class with no references to its enclosing class is returned from an instance method, it has a reference to this. Why?
当从实例方法返回不引用其封闭类的匿名类时,它具有对
考虑以下代码:
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 |
每个方法,尽管看起来调用相同的代码,但都在做不同的事情。在我看来,实例方法返回的是嵌套类,而静态方法返回的是静态嵌套类(作为静态成员,显然不能引用
考虑到没有引用封闭类这一事实,我看不到其中的好处。
幕后发生了什么?
匿名/内部类背后有一个设计原则:内部类的每个实例都属于外部类的一个实例。
省略对内部类的引用会改变垃圾收集的行为:按照实现方法,只要内部类是活动的,就不能对外部类进行垃圾收集。这支持了这样一种观点:没有外部类,内部类就不可能存在。
应用程序可能依赖于这种行为,例如通过创建一个临时文件并在析构函数中删除它。这样,只有当所有内部类都不存在时,文件才会被删除。
这也意味着当前行为不能更改,因为更改它可能会破坏现有的应用程序。
因此,当不需要引用时,应该始终将内部类标记为静态类,因为这可能导致一些不错的内存泄漏。
编辑:我想说的示例(对糟糕的代码质量表示抱歉):
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).
我只想说:它引用了
想象一下对程序的一个微小修改:
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(); } }; } } |
显然,在实例上下文中创建的对象需要引用
在最初的示例中,您没有以任何方式访问封闭实例,这并不意味着默认情况下该
在哪一点上,应该以其他方式作出决定?编译器是否应该检查
by the way:The comparison with
即使我们看不到任何可见的引用,它仍然存在。请参见下面的代码。
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(); } } |
结论: