Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

前菜

众所周知,java的内部类是可以访问到外部类的field的,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Fruit {
private String name;

public Origin origin(){
return new Origin();
}

class Origin{
String province;
String county;

public void info(){
System.out.println("fruit from "+ province + " "+county+" name:"+name); //可以访问到外部类的name属性
}
}
}

但是为什么呢?我们打印下origin对象的字段

1
2
3
4
5
6
7
8
public static void main(String[] args) {
Fruit fruit = new Fruit();
Origin origin = fruit.origin();

for (Field field : origin.getClass().getDeclaredFields()) {
System.out.println(field);
}
}

输出:

1
2
3
java.lang.String innerclass.Fruit$Origin.province
java.lang.String innerclass.Fruit$Origin.county
final innerclass.Fruit innerclass.Fruit$Origin.this$0

可以看到,除了Origin类自有的province和county字段外,编译时会生成一个this$0字段,由final修饰,类型是Fruit.

所以,内部类之所以能够访问到外部类的field,是因为它持有了一个外部类的引用this$0

稍微复杂的情况

当匿名类加上抽象类,会碰撞出怎样的火花呢?

定义一个抽象类Parent,它含有一个抽象内部类Child,同时有一个child1方法,返回一个Child对象:

1
2
3
4
5
6
7
public abstract class Parent {
protected abstract class Child{}
protected Child child1(){
return new Child() {
};
}
}

再定义一个ParentImpl类,继承自Parent, 同时有一个child2方法,也返回一个Child对象

1
2
3
4
5
6
public class ParentImpl extends Parent{
public Parent.Child child2(){
return new Child() {
};
}
}

我们定义一个ParentImpl对象,那么这个对象应该有两个可操作的方法,child1()和child2(), child1()是来自父类的方法,child2()是实现类自己的方法,这两个方法都是Child类型,但是会不会有什么不同呢?答案就在this$0.

我们分别去打印这两个返回对象的this$0

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws IllegalAccessException {
ParentImpl parentImpl = new ParentImpl();
Parent.Child child2 = parentImpl.child2(); //实现自子类的child
Field outField2 = getField(child2.getClass(),"this$0"); //子类内部类的this$0
System.out.println("子类new Child()的this$0:"+outField2);

Parent.Child child1 = parentImpl.child1(); //父类里实现的child
Field outField1 = getField(child1.getClass(),"this$0"); //父类内部类的this$0
System.out.println("父类new Child()的this$0:"+outField1);
}

输出:

1
2
子类new Child()的this$0:final ParentImpl ParentImpl$1.this$0
父类new Child()的this$0:final Parent Parent$1.this$0

可以看到,两个对象的this$0有所不同,子类new的Child的this$0是ParentImpl类型,父类new的Child的this$0是Parent类型,倒也合情合理。

那这个时候,假如说,我拿到了子类里的内部类对象,即child2, 同时我又有父类内部类的this$0,outField1, 我能否从child2利用反射拿到父对象呢?即

1
outField1.get(child2);

抛异常了:

1
2
3
4
5
6
7
Exception in thread "main" java.lang.IllegalArgumentException: Can not set final Parent field Parent$1.this$0 to ParentImpl$1
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:58)
at sun.reflect.UnsafeQualifiedObjectFieldAccessorImpl.get(UnsafeQualifiedObjectFieldAccessorImpl.java:38)
at java.lang.reflect.Field.get(Field.java:393)
at Main.main(Main.java:15)

那换一下呢?

1
outField2.get(child1);

也不行

1
2
3
4
5
6
7
Exception in thread "main" java.lang.IllegalArgumentException: Can not set final ParentImpl field ParentImpl$1.this$0 to Parent$1
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:58)
at sun.reflect.UnsafeQualifiedObjectFieldAccessorImpl.get(UnsafeQualifiedObjectFieldAccessorImpl.java:38)
at java.lang.reflect.Field.get(Field.java:393)
at Main.main(Main.java:12)

说白了就是,child2里面没有final Parent Parent$1.this$0这个field,你想get,就会抛异常,同理child1里面也没有final ParentImpl ParentImpl$1.this$0这个field,你想get,也会抛异常。

总结一下

上面说到的匿名内部类的问题,说白了是它的作用域的问题,父类里的抽象内部类,在父类里new,和在子类里new, this$0的指向是不同的。

评论