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文件 分别为 OuterClassInnerClass.classOuterClass.class。javap−cOuterClassInnerClass.classOuterClass.class。javap−cOuterClassInnerClass.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多了一个成员变量this0。这里有两个问题1.this0。这里有两个问题1.this0是如何被赋值的? 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会默认创建一个this0成员变量。我们来看下Anonymous反编译的情况。反编译查看生成了3个文件Anonymous0成员变量。我们来看下Anonymous反编译的情况。反编译查看生成了3个文件Anonymous1.class、AnonymousInnerClass.class和Anonymous.class。AnonymousInnerClass.class和Anonymous.class。Anonymous1.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”。但是并没有。原因是内部类对象持有了外部类对象的引用导致无法会回收