java私有域有何用_学以致用,通过字节码理解:Java的内部类与外部类之私有域访问...
目录:
内部类的定义及用处
打开字节码理解内部类
一、内部类的定义及用处
内部类(inner class)是定义在另一个类中的类。使用内部类,我们可以:
访问该类定义所在的作用域中的数据,包括私有的数据
可以对同一个包中的其他类隐藏起来
当想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous)内部类比较便捷
本文旨在讲解内部类与外部类可以相互访问对方的私有域的原理,内部类的用法等大家可以自行查阅(官网介绍简单明了:Nested Class);
二、打开字节码理解内部类
我们知道,内部类其实是Java语言的一种语法糖。经过编译会生成一个"外部类名$内部类名.class"的class文件。如下:

非常简单的一个类OuterCls,包含了一个InnerCls内部类。通过javac编译,我们可以看到列表中多了一个文件:OuterCls$InnerCls.class。
接着,我们通过javap -verbose查看生成的OuterCls.class:


1 $ javap -verbose OuterCls2 Warning: File ./OuterCls.class does not contain classOuterCls3 Classfile /Users/ntchan/code/demo/concepts/src/com/ntchan/nestedcls/OuterCls.class
4 Last modified Aug 14, 2018; size 434bytes5 MD5 checksum b9a1f41c67c8ae3be427c578ea205d206 Compiled from "OuterCls.java"
7 public classcom.ntchan.nestedcls.OuterCls8 minor version: 0
9 major version: 53
10 flags: (0x0021) ACC_PUBLIC, ACC_SUPER11 this_class: #3 //com/ntchan/nestedcls/OuterCls
12 super_class: #4 //java/lang/Object
13 interfaces: 0, fields: 1, methods: 2, attributes: 2
14 Constant pool:15 #1 = Fieldref #3.#18 //com/ntchan/nestedcls/OuterCls.outerField:I
16 #2 = Methodref #4.#19 //java/lang/Object."":()V
17 #3 = Class #20 //com/ntchan/nestedcls/OuterCls
18 #4 = Class #21 //java/lang/Object
19 #5 = Class #22 //com/ntchan/nestedcls/OuterCls$InnerCls
20 #6 =Utf8 InnerCls21 #7 =Utf8 InnerClasses22 #8 =Utf8 outerField23 #9 =Utf8 I24 #10 = Utf8
25 #11 =Utf8 ()V26 #12 =Utf8 Code27 #13 =Utf8 LineNumberTable28 #14 = Utf8 access$000
29 #15 = Utf8 (Lcom/ntchan/nestedcls/OuterCls;)I30 #16 =Utf8 SourceFile31 #17 =Utf8 OuterCls.java32 #18 = NameAndType #8:#9 //outerField:I
33 #19 = NameAndType #10:#11 //"":()V
34 #20 = Utf8 com/ntchan/nestedcls/OuterCls35 #21 = Utf8 java/lang/Object36 #22 = Utf8 com/ntchan/nestedcls/OuterCls$InnerCls37 {38 publiccom.ntchan.nestedcls.OuterCls();39 descriptor: ()V40 flags: (0x0001) ACC_PUBLIC41 Code:42 stack=2, locals=1, args_size=1
43 0: aload_044 1: invokespecial #2 //Method java/lang/Object."":()V
45 4: aload_046 5: iconst_547 6: putfield #1 //Field outerField:I
48 9: return
49 LineNumberTable:50 line 3: 0
51 line 4: 4
52
53 static int access$000(com.ntchan.nestedcls.OuterCls);54 descriptor: (Lcom/ntchan/nestedcls/OuterCls;)I55 flags: (0x1008) ACC_STATIC, ACC_SYNTHETIC56 Code:57 stack=1, locals=1, args_size=1
58 0: aload_059 1: getfield #1 //Field outerField:I
60 4: ireturn61 LineNumberTable:62 line 3: 0
63 }64 SourceFile: "OuterCls.java"
65 InnerClasses:66 #6= #5 of #3; //InnerCls=class com/ntchan/nestedcls/OuterCls$InnerCls of class com/ntchan/nestedcls/OuterCls
View Code
其中,我们发现OuterCls多了一个静态方法access$000:

我们看一下这个静态方法做了什么:
缺省修饰符,表示这个方法的域是包可见
这个静态方法只有一个参数:OuterCls
ACC_SYNTHETIC:表示这个方法是由编译器自动生成的
aload_0表示把局部变量表的第一个变量加载到操作栈
getfield 访问实例字段 outerField
ireturn 返回传参进来的OuterCls的outerFiled的值
好像发现了什么,对比代码,我们在内部类使用了外部类的私有域outerField,编译器就自动帮我们生成了一个仅包可见的静态方法来返回outerField的值。
接着,我们继续查看内部类InnerCls的字节码:


1 $ javap -verbose OuterCls\$InnerCls2 Warning: File ./OuterCls$InnerCls.class does not contain classOuterCls$InnerCls3 Classfile /Users/ntchan/code/demo/concepts/src/com/ntchan/nestedcls/OuterCls$InnerCls.class
4 Last modified Aug 14, 2018; size 648bytes5 MD5 checksum 344420034b48389a027a2f303cd2617c6 Compiled from "OuterCls.java"
7 classcom.ntchan.nestedcls.OuterCls$InnerCls8 minor version: 0
9 major version: 53
10 flags: (0x0020) ACC_SUPER11 this_class: #6 //com/ntchan/nestedcls/OuterCls$InnerCls
12 super_class: #7 //java/lang/Object
13 interfaces: 0, fields: 1, methods: 2, attributes: 2
14 Constant pool:15 #1 = Fieldref #6.#18 //com/ntchan/nestedcls/OuterCls$InnerCls.this$0:Lcom/ntchan/nestedcls/OuterCls;
16 #2 = Methodref #7.#19 //java/lang/Object."":()V
17 #3 = Fieldref #20.#21 //java/lang/System.out:Ljava/io/PrintStream;
18 #4 = Methodref #22.#23 //com/ntchan/nestedcls/OuterCls.access$000:(Lcom/ntchan/nestedcls/OuterCls;)I
19 #5 = Methodref #24.#25 //java/io/PrintStream.println:(I)V
20 #6 = Class #26 //com/ntchan/nestedcls/OuterCls$InnerCls
21 #7 = Class #29 //java/lang/Object
22 #8 = Utf8 this$0
23 #9 = Utf8 Lcom/ntchan/nestedcls/OuterCls;24 #10 = Utf8
25 #11 = Utf8 (Lcom/ntchan/nestedcls/OuterCls;)V26 #12 =Utf8 Code27 #13 =Utf8 LineNumberTable28 #14 =Utf8 printOuterField29 #15 =Utf8 ()V30 #16 =Utf8 SourceFile31 #17 =Utf8 OuterCls.java32 #18 = NameAndType #8:#9 //this$0:Lcom/ntchan/nestedcls/OuterCls;
33 #19 = NameAndType #10:#15 //"":()V
34 #20 = Class #30 //java/lang/System
35 #21 = NameAndType #31:#32 //out:Ljava/io/PrintStream;
36 #22 = Class #33 //com/ntchan/nestedcls/OuterCls
37 #23 = NameAndType #34:#35 //access$000:(Lcom/ntchan/nestedcls/OuterCls;)I
38 #24 = Class #36 //java/io/PrintStream
39 #25 = NameAndType #37:#38 //println:(I)V
40 #26 = Utf8 com/ntchan/nestedcls/OuterCls$InnerCls41 #27 =Utf8 InnerCls42 #28 =Utf8 InnerClasses43 #29 = Utf8 java/lang/Object44 #30 = Utf8 java/lang/System45 #31 =Utf8 out46 #32 = Utf8 Ljava/io/PrintStream;47 #33 = Utf8 com/ntchan/nestedcls/OuterCls48 #34 = Utf8 access$000
49 #35 = Utf8 (Lcom/ntchan/nestedcls/OuterCls;)I50 #36 = Utf8 java/io/PrintStream51 #37 =Utf8 println52 #38 =Utf8 (I)V53 {54 final com.ntchan.nestedcls.OuterCls this$0;55 descriptor: Lcom/ntchan/nestedcls/OuterCls;56 flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC57
58 com.ntchan.nestedcls.OuterCls$InnerCls(com.ntchan.nestedcls.OuterCls);59 descriptor: (Lcom/ntchan/nestedcls/OuterCls;)V60 flags: (0x0000)61 Code:62 stack=2, locals=2, args_size=2
63 0: aload_064 1: aload_165 2: putfield #1 //Field this$0:Lcom/ntchan/nestedcls/OuterCls;
66 5: aload_067 6: invokespecial #2 //Method java/lang/Object."":()V
68 9: return
69 LineNumberTable:70 line 5: 0
71
72 public voidprintOuterField();73 descriptor: ()V74 flags: (0x0001) ACC_PUBLIC75 Code:76 stack=2, locals=1, args_size=1
77 0: getstatic #3 //Field java/lang/System.out:Ljava/io/PrintStream;
78 3: aload_079 4: getfield #1 //Field this$0:Lcom/ntchan/nestedcls/OuterCls;
80 7: invokestatic #4 //Method com/ntchan/nestedcls/OuterCls.access$000:(Lcom/ntchan/nestedcls/OuterCls;)I
81 10: invokevirtual #5 //Method java/io/PrintStream.println:(I)V
82 13: return
83 LineNumberTable:84 line 7: 0
85 line 8: 13
86 }87 SourceFile: "OuterCls.java"
88 InnerClasses:89 #27= #6 of #22; //InnerCls=class com/ntchan/nestedcls/OuterCls$InnerCls of class com/ntchan/nestedcls/OuterCls
View Code
首先,我们发现编译器自动生成了一个声明为final的成员:

我们知道,final修饰的成员都是编译器可以确定的常量。经过final修饰的变量,都会放到class的常量池。
然后再看一下编译器自动生成的构造函数:

具体的字节码指令我就不再一一贴出来,我简单解释一下,这个构造函数通过外部传参OuterCls实例,赋值给this$0(上面那个被final修饰的变量)
最后看一下我们的printOutField方法:

我们看到,原本调用outerField的地方,变成了OuterField.access$000(this$0),意思就是,通过OuterField的静态方法,返回this$0的OuterField。
总的来讲,内部类访问外部类的私有成员的原理,是通过编译器分别给外部类自动生成访问私有成员的静态方法access$000及给内部类自动生成外部类的final引用、外部类初始化的构造函数及修改调用外部类私有成员的代码为调用外部类包可见的access$000实现的。同理,匿名内部类、静态内部类都可以通过这种方法分析实现原理
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
