學以致用,通過字節碼理解:Java的內部類與外部類之私有域訪問


目錄:

  • 內部類的定義及用處
  • 打開字節碼理解內部類

 

一、內部類的定義及用處

內部類(inner class)是定義在另一個類中的類。使用內部類,我們可以:

  • 訪問該類定義所在的作用域中的數據,包括私有的數據
  • 可以對同一個包中的其他類隱藏起來
  • 當想要定義一個回調函數且不想編寫大量代碼時,使用匿名(anonymous)內部類比較便捷

本文旨在講解內部類與外部類可以相互訪問對方的私有域的原理,內部類的用法等大家可以自行查閱(官網介紹簡單明了:Nested Class);

 

二、打開字節碼理解內部類

我們知道,內部類其實是Java語言的一種語法糖。經過編譯會生成一個"外部類名$內部類名.class"的class文件。如下:


非常簡單的一個類OuterCls,包含了一個InnerCls內部類。通過javac編譯,我們可以看到列表中多了一個文件:OuterCls$InnerCls.class。

接着,我們通過javap -verbose查看生成的OuterCls.class:

 1 $ javap -verbose OuterCls
 2 Warning: File ./OuterCls.class does not contain class OuterCls
 3 Classfile /Users/ntchan/code/demo/concepts/src/com/ntchan/nestedcls/OuterCls.class
 4   Last modified Aug 14, 2018; size 434 bytes
 5   MD5 checksum b9a1f41c67c8ae3be427c578ea205d20
 6   Compiled from "OuterCls.java"
 7 public class com.ntchan.nestedcls.OuterCls
 8   minor version: 0
 9   major version: 53
10   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
11   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."<init>":()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               InnerCls
21    #7 = Utf8               InnerClasses
22    #8 = Utf8               outerField
23    #9 = Utf8               I
24   #10 = Utf8               <init>
25   #11 = Utf8               ()V
26   #12 = Utf8               Code
27   #13 = Utf8               LineNumberTable
28   #14 = Utf8               access$000
29   #15 = Utf8               (Lcom/ntchan/nestedcls/OuterCls;)I
30   #16 = Utf8               SourceFile
31   #17 = Utf8               OuterCls.java
32   #18 = NameAndType        #8:#9          // outerField:I
33   #19 = NameAndType        #10:#11        // "<init>":()V
34   #20 = Utf8               com/ntchan/nestedcls/OuterCls
35   #21 = Utf8               java/lang/Object
36   #22 = Utf8               com/ntchan/nestedcls/OuterCls$InnerCls
37 {
38   public com.ntchan.nestedcls.OuterCls();
39     descriptor: ()V
40     flags: (0x0001) ACC_PUBLIC
41     Code:
42       stack=2, locals=1, args_size=1
43          0: aload_0
44          1: invokespecial #2                  // Method java/lang/Object."<init>":()V
45          4: aload_0
46          5: iconst_5
47          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;)I
55     flags: (0x1008) ACC_STATIC, ACC_SYNTHETIC
56     Code:
57       stack=1, locals=1, args_size=1
58          0: aload_0
59          1: getfield      #1                  // Field outerField:I
60          4: ireturn
61       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:

我們看一下這個靜態方法做了什么:

  1. 缺省修飾符,表示這個方法的域是包可見
  2. 這個靜態方法只有一個參數:OuterCls
  3. ACC_SYNTHETIC:表示這個方法是由編譯器自動生成的
  4. aload_0表示把局部變量表的第一個變量加載到操作棧
  5. getfield 訪問實例字段 outerField
  6. ireturn 返回傳參進來的OuterCls的outerFiled的值

好像發現了什么,對比代碼,我們在內部類使用了外部類的私有域outerField,編譯器就自動幫我們生成了一個僅包可見的靜態方法來返回outerField的值。

接着,我們繼續查看內部類InnerCls的字節碼:

 1 $ javap -verbose OuterCls\$InnerCls
 2 Warning: File ./OuterCls$InnerCls.class does not contain class OuterCls$InnerCls
 3 Classfile /Users/ntchan/code/demo/concepts/src/com/ntchan/nestedcls/OuterCls$InnerCls.class
 4   Last modified Aug 14, 2018; size 648 bytes
 5   MD5 checksum 344420034b48389a027a2f303cd2617c
 6   Compiled from "OuterCls.java"
 7 class com.ntchan.nestedcls.OuterCls$InnerCls
 8   minor version: 0
 9   major version: 53
10   flags: (0x0020) ACC_SUPER
11   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."<init>":()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               <init>
25   #11 = Utf8               (Lcom/ntchan/nestedcls/OuterCls;)V
26   #12 = Utf8               Code
27   #13 = Utf8               LineNumberTable
28   #14 = Utf8               printOuterField
29   #15 = Utf8               ()V
30   #16 = Utf8               SourceFile
31   #17 = Utf8               OuterCls.java
32   #18 = NameAndType        #8:#9          // this$0:Lcom/ntchan/nestedcls/OuterCls;
33   #19 = NameAndType        #10:#15        // "<init>":()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$InnerCls
41   #27 = Utf8               InnerCls
42   #28 = Utf8               InnerClasses
43   #29 = Utf8               java/lang/Object
44   #30 = Utf8               java/lang/System
45   #31 = Utf8               out
46   #32 = Utf8               Ljava/io/PrintStream;
47   #33 = Utf8               com/ntchan/nestedcls/OuterCls
48   #34 = Utf8               access$000
49   #35 = Utf8               (Lcom/ntchan/nestedcls/OuterCls;)I
50   #36 = Utf8               java/io/PrintStream
51   #37 = Utf8               println
52   #38 = Utf8               (I)V
53 {
54   final com.ntchan.nestedcls.OuterCls this$0;
55     descriptor: Lcom/ntchan/nestedcls/OuterCls;
56     flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
57 
58   com.ntchan.nestedcls.OuterCls$InnerCls(com.ntchan.nestedcls.OuterCls);
59     descriptor: (Lcom/ntchan/nestedcls/OuterCls;)V
60     flags: (0x0000)
61     Code:
62       stack=2, locals=2, args_size=2
63          0: aload_0
64          1: aload_1
65          2: putfield      #1                  // Field this$0:Lcom/ntchan/nestedcls/OuterCls;
66          5: aload_0
67          6: invokespecial #2                  // Method java/lang/Object."<init>":()V
68          9: return
69       LineNumberTable:
70         line 5: 0
71 
72   public void printOuterField();
73     descriptor: ()V
74     flags: (0x0001) ACC_PUBLIC
75     Code:
76       stack=2, locals=1, args_size=1
77          0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
78          3: aload_0
79          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實現的。同理,匿名內部類、靜態內部類都可以通過這種方法分析實現原理

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM