当从实例方法返回没有引用其封闭类的匿名类时,它引用了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 :(得分:7)
匿名/内部类背后有一个设计原则:内部类的每个实例都属于外部类的实例。
抛弃对内部类的引用会改变垃圾收集的行为:它的实现方式,只要内部类存活,外部类就不能被垃圾收集。
这支持了在没有外部类的情况下内部类不能存在的想法。
应用程序可能依赖于此行为,例如通过创建临时文件并在析构函数中删除它。这样,只有在所有内部类都消失后才会删除该文件。
这也意味着无法更改当前行为,因为更改它可能会破坏现有应用程序。
因此,当您不需要引用时,应始终将内部类标记为静态,因为这可能会导致一些不错的内存泄漏。
编辑: 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的实例相关联,称为立即封闭 我的一个例子。如果是,则立即封闭对象的实例 任何,在创建对象时确定(第15.9.2节)。
答案 1 :(得分: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
,即使两个对象都是 “context”,只要你没有相应地在返回的对象中实现自己的equals
方法
答案 2 :(得分:0)
即使我们看不到任何可见的参考,它仍然存在。请参阅下面的代码。
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的建议,在创建匿名/方法本地类时,会添加一些额外的代码来保存封闭的类实例引用。
这也可以在反编译输出中看到。
// 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
或向构造函数添加默认超类构造函数super()
,该构造函数不显式调用任何this()
自构造函数或super()
构造函数。添加封闭类型的引用也是如此,那里没有魔法。我们可以很容易地定义这样一个类,编译器只是为我们做这件事让我们的生活更轻松。modifier
输出,可以断定它们只是方法本地类(所有类修饰符public,protected,private,abstract,static)在方法中失去意义。他们只是被称为匿名课程,他们称之为方法本地课程。