1. 非静态内部类是如何引用外部类this对象的
Java内部类分为静态内部类和非静态内部类。它们有一个比较大的区别在于,非静态内部类拥有外部类的this对象的引用,从而使得非静态内部类可以访问外部类的成员函数,成员变量。这个结论我们大家都比较清楚,那么原理大家都懂吗?这篇文章我讲通过反编译的方法一探其中的奥秘
public class OuterClass {
public void test() {
System.out.println("test");
}
class InnerClass {
public void test() {
OuterClass.this.test();
}
}
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
InnerClass innerClass = outerClass.new InnerClass();
innerClass.test();
}
}
InnerClass的test()方法通过OuterClass.this对象直接调用外部类的test()方法。那么OuterClass.this对象到底是什么,它又是怎么初始化到InnerClass对象里的呢。下面通过查看class字节码指令来一探究竟
在classes目录中我们可以看到分别生成了两个class文件 分别为 OuterClass InnerClass.class
Compiled from "OuterClass.java"
class com.peter.tips.collections.OuterClass$InnerClass {
final com.peter.tips.collections.OuterClass this$0;
com.peter.tips.collections.OuterClass$InnerClass(com.peter.tips.collections.OuterClass);
Code:
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:Lcom/peter/tips/collections/OuterClass;
5: aload_0
6: invokespecial #2 // Method java/lang/Object."<init>":()V
9: return
public void test();
Code:
0: aload_0
1: getfield #1 // Field this$0:Lcom/peter/tips/collections/OuterClass;
4: pop
5: invokestatic #3 // Method com/peter/tips/collections/OuterClass.test:()V
8: return
}
通过反编译InnerClass的字节码。我们发现InnerClass多了一个成员变量this 0是如何被赋值的? 2.this$0是否就是OuterClass.this对象呢?
1. this$0赋值过程
- 我们来观察下InnerClass的构造函数OuterClass$InnerClass(com.peter.tips.collections.OuterClass)。字节码
//获取构造函数的参数OuterClass
1: aload_1
//赋值给this$0对象
2: putfield #1 // Field this$0:Lcom/peter/tips/collections/OuterClass;
正是通过传入的OuterClass对象给this$0赋值。
- 反编译OuterClass的main方法,我们看看outerClass.new InnerClass()这行代码做了什么
public static void main(java.lang.String[]);
Code:
//创建OuterClass对象
0: new #5 // class com/peter/tips/collections/OuterClass
//对象再次压入栈
3: dup
//初始化OuterClass
4: invokespecial #6 // Method "<init>":()V
//OuterClass对象赋值给outerClass变量
7: astore_1
//创建InnerClass对象
8: new #7 // class com/peter/tips/collections/OuterClass$InnerClass
//对象再次压入栈
11: dup
//outClass对象压入栈
12: aload_1
//outClass对象压入栈
13: dup
//调用getClass()方法
14: invokevirtual #8 // Method java/lang/Object.getClass:()Ljava/lang/Class;
//出栈
17: pop
//初始化InnerClass 相当于 new InnerClass(outerClass)
18: invokespecial #9 // Method com/peter/tips/collections/OuterClass$InnerClass."<init>":(Lcom/peter/tips/collections/OuterClass;)V
//把创建的InnerClass对象赋值给innerClass变量 innerClass = new InnerClass(outerClass)
21: astore_2
//innerClass对象入栈
22: aload_2
//调用innerClass的test()方法
23: invokevirtual #10 // Method com/peter/tips/collections/OuterClass$InnerClass.test:()V
26: return
综上,我们知道创建内部类对象,下面两个代码块是等价的
OuterClass outerClass = new OuterClass();
InnerClass innerClass = outerClass.new InnerClass();
OuterClass outerClass = new OuterClass();
InnerClass innerClass = new InnerClass(outerClass);
2. this$0是否就是OuterClass.this对象
前面可以知道this$0对象其实就是新建的OuterClass对象,大胆的猜测下结果,因为代码 只创建了一个OuterClass对象,他们指向的肯定是同一个对象了。通过查看InnerClass的test()字节码也可以佐证这个结论
InnerClass
public void test();
Code:
//InnerClass对象入栈
0: aload_0
//获取到this$0对象
1: getfield #1 // Field this$0:Lcom/peter/tips/collections/OuterClass;
//调用this$0的test()方法 == OuterClass.this.test()
4: invokevirtual #3 // Method com/peter/tips/collections/OuterClass.test:()V
7: return
当然我们还可以通过反射来证明OuterClass.this、this$0的存在
System.out.println(innerClass.getClass().getDeclaredField("this$0"));
final com.peter.tips.nest.OuterClass com.peter.tips.nest.OuterClass$InnerClass.this$0
2. 匿名内部类使用外部参数为什么要用final
我们都知道如果在方法内创建匿名内部类,如果在匿名内部类中使用了方法的参数,或者局部变量。它们需要被定义成final类型。这是为什么呢?我们来看以下代码
public class Anonymous {
public void test(final int i,final String str){
String j ="hello world";
new InnerClass(){
@Override
void run() {
System.out.println("j="+j+";i="+i+";str="+str);
}
}.run();
// j="Hi world";
}
public static void main(String[] args) {
Anonymous anonymous = new Anonymous();
anonymous.test(1,"hello");
}
class InnerClass{
void run(){
}
}
}
前面OuterClass的InnerClass会默认创建一个this 1.class、Anonymous 1.class正是Anonymous的test()方法中创建的匿名内部类对象。
Compiled from "Anonymous.java"
class com.peter.tips.nest.Anonymous$1 extends com.peter.tips.nest.Anonymous$InnerClass {
final java.lang.String val$j;
final int val$i;
final java.lang.String val$str;
final com.peter.tips.nest.Anonymous this$0;
com.peter.tips.nest.Anonymous$1(com.peter.tips.nest.Anonymous, java.lang.String, int, java.lang.String);
Code:
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:Lcom/peter/tips/nest/Anonymous;
5: aload_0
6: aload_2
7: putfield #2 // Field val$j:Ljava/lang/String;
10: aload_0
11: iload_3
12: putfield #3 // Field val$i:I
15: aload_0
16: aload 4
18: putfield #4 // Field val$str:Ljava/lang/String;
21: aload_0
22: aload_1
23: invokespecial #5 // Method com/peter/tips/nest/Anonymous$InnerClass."<init>":(Lcom/peter/tips/nest/Anonymous;)V
26: return
void run();
Code:
0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
3: new #7 // class java/lang/StringBuilder
6: dup
7: invokespecial #8 // Method java/lang/StringBuilder."<init>":()V
10: ldc #9 // String j=
12: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: aload_0
16: getfield #2 // Field val$j:Ljava/lang/String;
19: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: ldc #11 // String ;i=
24: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
27: aload_0
28: getfield #3 // Field val$i:I
31: invokevirtual #12 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
34: ldc #13 // String ;str=
36: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
39: aload_0
40: getfield #4 // Field val$str:Ljava/lang/String;
43: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
46: invokevirtual #14 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
49: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
52: return
}
通过查看字节码我们可以看到,不管是形参还是局部变量,最终都被传到构造函数的形参里去了。而且Anonymous$1成员变量定义的都是final类型。所以外部参数也需要是final的才行
3. 非静态内部类是如何导致内存泄漏的
public class MemoryLeak {
public static void main(String[] args) {
MemoryLeak memoryLeak = new MemoryLeak();
InnerClass innerClass = memoryLeak.new InnerClass();
memoryLeak = null;
System.gc();
}
class InnerClass {
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize");
}
}
我们预期的程序将会打印”finalize”。但是并没有。原因是内部类对象持有了外部类对象的引用导致无法会回收