java内部类引用局部变量与外部类成员变量实例分析
导读:本文共5434字符,通常情况下阅读需要18分钟。同时您也可以点击右侧朗读,来听本文内容。按键盘←(左) →(右) 方向键可以翻页。
摘要: 假如我们有以下的代码:interfacePrinter{publicvoidprint();}classMyApplication{privateintfield=10;publicvoidprint(finalIntegerparam){finallonglocal=100;finallonglocal2=param.longValue()+100;Print... ...
目录
(为您整理了一些要点),点击可以直达。- (7)://CompiledfromPrinter.java(
- (13)://Fielddescriptor#10J
- (16)://Fielddescriptor#12Ljava/l
- (20)://Stack:3,Locals:5
- (22):0aload_0[this]
- (23):1aload_1[arg0]
- (28):10aload_0[this]
- (29):11aload4[arg2]
- (31):16aload_0[this]
- (32):17invokespecialjava.lang.Ob
- (33):20return
- (40)://Methoddescriptor#24()V
- (41)://Stack:4,Locals:1
- (47):11newjava.lang.StringBuilde
- (48):14dup
- (49):15ldc<String"Local2
- (51):20aload_0[this]
假如我们有以下的代码:
这里因为param要在匿名内部类的print()方法中使用,因而它要用final修饰;local/local2是局部变量,因而也需要final修饰;而field是外部类MyApplication的字段,因而不需要final修饰。这种设计是基于什么理由呢?
我想这个问题应该从Java是如何实现匿名内部类的。其中有两点:
1、匿名内部类可以使用外部类的变量(局部或成员变来那个)。
2、匿名内部类中不同的方法可以共享这些变量。
根据这两点信息我们就可以分析,可能这些变量会在匿名内部类的字段中保存着,并且在构造的时候将他们的值/引用传入内部类。这样就可以保证同时实现上述两点了。
事实上,Java就是这样设计的,并且所谓匿名类,其实并不是匿名的,只是编译器帮我们命名了而已。这点我们可以通过这两个类编译出来的字节码看出来:
//CompiledfromPrinter.java(version1.6:50.0,superbit)
classlevin.test.anonymous.MyApplication$1implementslevin.test.anonymous.Printer{
//Fielddescriptor#8Llevin/test/anonymous/MyApplication;
finalsyntheticlevin.test.anonymous.MyApplicationthis$0;
//Fielddescriptor#10J
privatefinalsyntheticlongval$local2;
//Fielddescriptor#12Ljava/lang/Integer;
privatefinalsyntheticjava.lang.Integerval$param;
//Methoddescriptor#14(Llevin/test/anonymous/MyApplication;JLjava/lang/Integer;)V
//Stack:3,Locals:5
MyApplication$1(levin.test.anonymous.MyApplicationarg0,longarg1,java.lang.Integerarg2);
0aload_0[this]
1aload_1[arg0]
2putfieldlevin.test.anonymous.MyApplication$1.this$0:levin.test.anonymous.MyApplication[16]
5aload_0[this]
6lload_2[arg1]
7putfieldlevin.test.anonymous.MyApplication$1.val$local2:long[18]
10aload_0[this]
11aload4[arg2]
13putfieldlevin.test.anonymous.MyApplication$1.val$param:java.lang.Integer[20]
16aload_0[this]
17invokespecialjava.lang.Object()[22]
20return
Linenumbers:
[pc:0,line:1]
[pc:16,line:13]
Localvariabletable:
[pc:0,pc:21]local:thisindex:0type:newlevin.test.anonymous.MyApplication(){}
//Methoddescriptor#24()V
//Stack:4,Locals:1
publicvoidprint();
0getstaticjava.lang.System.out:java.io.PrintStream[30]
3ldc<String"Localvalue:100">[36]
5invokevirtualjava.io.PrintStream.println(java.lang.String):void[38]
8getstaticjava.lang.System.out:java.io.PrintStream[30]
11newjava.lang.StringBuilder[44]
14dup
15ldc<String"Local2value:">[46]
17invokespecialjava.lang.StringBuilder(java.lang.String)[48]
20aload_0[this]
21getfieldlevin.test.anonymous.MyApplication$1.val$local2:long[18]
24invokevirtualjava.lang.StringBuilder.append(long):java.lang.StringBuilder[50]
27invokevirtualjava.lang.StringBuilder.toString():java.lang.String[54]
30invokevirtualjava.io.PrintStream.println(java.lang.String):void[38]
33getstaticjava.lang.System.out:java.io.PrintStream[30]
36newjava.lang.StringBuilder[44]
39dup
40ldc<String"Parameter:">[58]
42invokespecialjava.lang.StringBuilder(java.lang.String)[48]
45aload_0[this]
46getfieldlevin.test.anonymous.MyApplication$1.val$param:java.lang.Integer[20]
49invokevirtualjava.lang.StringBuilder.append(java.lang.Object):java.lang.StringBuilder[60]
52invokevirtualjava.lang.StringBuilder.toString():java.lang.String[54]
55invokevirtualjava.io.PrintStream.println(java.lang.String):void[38]
58getstaticjava.lang.System.out:java.io.PrintStream[30]
61newjava.lang.StringBuilder[44]
64dup
65ldc<String"Fieldvalue:">[63]
67invokespecialjava.lang.StringBuilder(java.lang.String)[48]
70aload_0[this]
71getfieldlevin.test.anonymous.MyApplication$1.this$0:levin.test.anonymous.MyApplication[16]
74invokestaticlevin.test.anonymous.MyApplication.access$0(levin.test.anonymous.MyApplication):int[65]
77invokevirtualjava.lang.StringBuilder.append(int):java.lang.StringBuilder[71]
80invokevirtualjava.lang.StringBuilder.toString():java.lang.String[54]
83invokevirtualjava.io.PrintStream.println(java.lang.String):void[38]
86return
Linenumbers:
[pc:0,line:16]
[pc:8,line:17]
[pc:33,line:18]
[pc:58,line:19]
[pc:86,line:20]
Localvariabletable:
[pc:0,pc:87]local:thisindex:0type:newlevin.test.anonymous.MyApplication(){}
Innerclasses:
[innerclassinfo:#1levin/test/anonymous/MyApplication$1,outerclassinfo:#0
innername:#0,accessflags:0default]
EnclosingMethod:#66#77levin/test/anonymous/MyApplication.print(Ljava/lang/Integer;)V
}
从这两段字节码中可以看出,编译器为我们的匿名类起了一个叫MyApplication$1的名字,它包含了三个final字段(这里synthetic修饰符是指这些字段是由编译器生成的,它们并不存在于源代码中):
MyApplication的应用this$0
long值val$local2
Integer引用val$param
这些字段在构造函数中赋值,而构造函数则是在MyApplication.print()方法中调用。
由此,我们可以得出一个结论:Java对匿名内部类的实现是通过编译器来支持的,即通过编译器帮我们产生一个匿名类的类名,将所有在匿名类中用到的局部变量和参数做为内部类的final字段,同是内部类还会引用外部类的实例。其实这里少了local的变量,这是因为local是编译器常量,编译器对它做了替换的优化。
其实Java中很多语法都是通过编译器来支持的,而在虚拟机/字节码上并没有什么区别,比如这里的final关键字,其实细心的人会发现在字节码中,param参数并没有final修饰,而final本身的很多实现就是由编译器支持的。类似的还有Java中得泛型和逆变、协变等。这是题外话。
有了这个基础后,我们就可以来分析为什么有些要用final修饰,有些却不用的问题。
首先我们来分析local2变量,在”匿名类”中,它是通过构造函数传入到”匿名类”字段中的,因为它是基本类型,因而在够着函数中赋值时(撇开对函数参数传递不同虚拟机的不同实现而产生的不同效果),它事实上只是值的拷贝;因而加入我们可以在”匿名类”中得print()方法中对它赋值,那么这个赋值对外部类中得local2变量不会有影响,而程序员在读代码中,是从上往下读的,所以很容易误认为这段代码赋值会对外部类中得local2变量本身产生影响,何况在源码中他们的名字都是一样的,所以我认为了避免这种confuse导致的一些问题,Java设计者才设计出了这样的语法。
对引用类型,其实也是一样的,因为引用的传递事实上也只是传递引用的数值(简单的可以理解成为地址),因而对param,如果可以在”匿名类”中赋值,也不会在外部类的print()后续方法产生影响。虽然这样,我们还是可以在内部类中改变引用内部的值的,如果引用类型不是只读类型的话;在这里Integer是只读类型,因而我们没法这样做。
java内部类引用局部变量与外部类成员变量实例分析的详细内容,希望对您有所帮助,信息来源于网络。