java内部类引用局部变量与外部类成员变量实例分析(java,编程语言)

时间:2024-05-09 13:53:54 作者 : 石家庄SEO 分类 : 编程语言
  • TAG :

    java%E5%86%85%E9%83%A8%E7%B1%BB%E5%BC%95%E7%94%A8%E5%B1%80%E9%83%A8%E5%8F%98%E9%87%8F%E4%B8%8E%E5%A4%96%E9%83%A8%E7%B1%BB%E6%88%90%E5%91%98%E5%8F%98%E9%87%8F%E5%AE%9E%E4%BE%8B%E5%88%86%E6%9E%90

假如我们有以下的代码:

这里因为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内部类引用局部变量与外部类成员变量实例分析的详细内容,希望对您有所帮助,信息来源于网络。
上一篇:小程序制作为什么广受欢迎下一篇:

16 人围观 / 0 条评论 ↓快速评论↓

(必须)

(必须,保密)

阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18