学Java 虽然时间不算太长,但是对一些原理性的东西很感兴趣。今天分析了一下匿名内部类调用构造方法的原理,希望高手拍砖。
因为匿名内部类没有名字这个特殊性质,所以我们无从给它指定构造方法,构造方法必须和类名同名,类名都没有,构造方法就无从谈起了。但是匿名内部类可以通过直接调用父类的构造方法实现初始化,当然要求父类构造方法对它父类中定义的成员变量进行初始化。这里用一个例子看创建匿名内部类的时候父类的构造方法到底是如何调用的。
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 Main { public static void main(String[] args) { InnerTest inner = new InnerTest(); Test t = inner.get( 3 ); System.out.println(t.getI()); } } class Test { //超类 private int i; public Test( int i) { this .i = i; } public int getI() { return i; } } class InnerTest { //用于内部类的测试 public Test get( int x) { return new Test(x) { //创建匿名内部类,调用父类的构造方法 @Override public int getI() { return super .getI() * 10 ; } }; } } |
编译得到4个class文件,这里只需要关注InnerTest.class 和 InnerTest$1.class。这里InnerTest$1.class是匿名内部类的class文件,InnerTest.class是InnerTest类的class文件。我们先看InnerTest$1.class的内容:
javap -c InnerTest$1 > InnerTest$1.txt
得到代码如下 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
Compiled from "Main.java" class InnerTest$ 1 extends Test{ final InnerTest this $ 0 ; InnerTest$ 1 (InnerTest, int ); Code: 0 : aload_0 1 : aload_1 2 : putfield # 1 ; //Field this$0:LInnerTest; 5 : aload_0 6 : iload_2 7 : invokespecial # 2 ; //Method Test."<init>":(I)V 10 : return public int getI(); Code: 0 : aload_0 1 : invokespecial # 3 ; //Method Test.getI:()I 4 : bipush 10 6 : imul 7 : ireturn } |
很明显,我们的匿名内部类有了名字 InnerTest$1 ,而且是继承自 Test
class InnerTest$1 extends Test
这个类中有一个成员final InnerTest this$0;我想这应该是该内部类所在的外部类InnerTest的引用
这个匿名内部类的构造方法是:
InnerTest$1(InnerTest, int);
一个是InnerTest类型,也就是该类外部类的引用,调用的时候应该是把外部类对象的this指针传给它,这样就内部类就可以直接访问外部类的成员了。
另一个就是int类型的,应该是对i进行初始化用的。
看到下面这行:
7: invokespecial #2; //Method Test."<init>":(I)V
现在应该清楚了,这是调用了父类Test的构造方法。
下面再看InnerTest是如何实现的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
Compiled from "Main.java" class InnerTest extends java.lang.Object{ InnerTest(); Code: 0 : aload_0 1 : invokespecial # 1 ; //Method java/lang/Object."<init>":()V 4 : return public Test get( int ); Code: 0 : new # 2 ; //class InnerTest$1 3 : dup 4 : aload_0 5 : iload_1 6 : invokespecial # 3 ; //Method InnerTest$1."<init>":(LInnerTest;I)V 9 : areturn } |
InnerTest的get方法是关键。
这里首先new InnerTest$1,后面调用了InnerTest$1的构造方法,并把两个参数传了进去。
这样一切都清楚了。
结论:其实匿名内部类也没有什么特别的地方,编译之后它有了名字,有了构造方法,就是一个正常的类了。
有理解不对的地方,请大家指正。