バイトコードの観点から見るJava内部クラスと外部クラスの相互アクセス

23751 ワード

Javaのnon-static内部クラスはなぜ外部クラスの変数にアクセスできるのですか?Java中外部クラスはなぜ内部クラスのprivate変数にアクセスできるのですか?この2つの問題は私を悩ませてしばらくして、いくつかのネット上の答えを調べて、大部分は“閉包”の概念から着手して、理解するのはとても骨が折れるので、別の角度からこの問題を説明することができますか?「本当に起きられないプログラマーはバイトごとに手のひらを指すべきだ」という言葉がありますが、Javaプログラムの「バイトごと」を理解するのは比較的簡単です.Javaコードのbytecodeで分析します.
 
 1 public class Test
 2 {
 3     public static void main(String[] args)
 4     {
 5         new Test().initData();
 6     }
 7 
 8     private void initData()
 9     {
10         new A().privateVar = 0;
11         new B().privateVar = 0;
12     }
13 
14     // non-static inner class A
15     private class A
16     {
17         private int privateVar;
18         int defaultVar;
19         protected int protectedVar;
20         public int publicVar;
21     }
22 
23     // static inner class B
24     private static class B
25     {
26         private int privateVar;
27         int defaultVar;
28         protected int protectedVar;
29         public int publicVar;
30     }
31 }

 
Java内部クラスは個別にコンパイルされるため.classファイル、javapコマンドで各を逆コンパイルします.classファイルが真相を探る.
non-static内部クラスAのbytecodeは以下の通りである.
 1 E:\workspace\testClass\bin>javap -c Test$A
 2 Compiled from "Test.java"
 3 class Test$A extends java.lang.Object{
 4 int defaultVar;
 5 
 6 protected int protectedVar;
 7 
 8 public int publicVar;
 9 
10 final Test this$0;
11 
12 Test$A(Test, Test$A);
13   Code:
14    0:   aload_0
15    1:   aload_1
16    2:   invokespecial   #25; //Method "<init>":(LTest;)V
17    5:   return
18 
19 static void access$1(Test$A, int);
20   Code:
21    0:   aload_0
22    1:   iload_1
23    2:   putfield        #29; //Field privateVar:I
24    5:   return
25 
26 }

static内部クラスBのbytecodeは以下の通りである.
 1 E:\workspace\testClass\bin>javap -c Test$B
 2 Compiled from "Test.java"
 3 class Test$B extends java.lang.Object{
 4 int defaultVar;
 5 
 6 protected int protectedVar;
 7 
 8 public int publicVar;
 9 
10 Test$B(Test$B);
11   Code:
12    0:   aload_0
13    1:   invokespecial   #20; //Method "<init>":()V
14    4:   return
15 
16 static void access$1(Test$B, int);
17   Code:
18    0:   aload_0
19    1:   iload_1
20    2:   putfield        #23; //Field privateVar:I
21    5:   return
22 
23 }

bytecodeから明らかなようにnon-static内部クラスAのデフォルト構造関数は実質的に2つのパラメータを伝達し,1つ目は外部クラスTestオブジェクトの参照であり,内部クラスAではfinalオブジェクトでこの参照を持つ(もう1つのパラメータは戻り値、Aの参照であり、本明細書で説明したトピックとは無関係である)が、static内部クラスBのデフォルトコンストラクション関数は外部クラスTestオブジェクトへの参照を持たない.これにより、non-static内部クラスは、外部クラスへのアクセスを隠蔽することによって完了する最初の質問に答える.
AとBには内部クラスprivate変数に対するaccess静的方法がある.(注:private変数へのアクセスがなければ、コンパイラはaccessメソッドを最適化するので、外部クラスが内部クラスprivate変数にアクセスできるコードが存在しなければならない)では、このメソッドは外部クラスが内部クラスprivate変数にアクセスできる理由ではないでしょうか.外部クラスTestを逆コンパイルする.classファイルは次のようになります.
 1 E:\workspace\testClass\bin>javap -verbose Test
 2 Compiled from "Test.java"
 3 public class Test extends java.lang.Object
 4   SourceFile: "Test.java"
 5   InnerClass:
 6    #42= #22 of #1; //A=class Test$A of class Test
 7    #43= #31 of #1; //B=class Test$B of class Test
 8   minor version: 0
 9   major version: 50
10   Constant pool:
11 const #1 = class        #2;     //  Test
12 const #2 = Asciz        Test;
13 const #3 = class        #4;     //  java/lang/Object
14 const #4 = Asciz        java/lang/Object;
15 const #5 = Asciz        <init>;
16 const #6 = Asciz        ()V;
17 const #7 = Asciz        Code;
18 const #8 = Method       #3.#9;  //  java/lang/Object."<init>":()V
19 const #9 = NameAndType  #5:#6;//  "<init>":()V
20 const #10 = Asciz       LineNumberTable;
21 const #11 = Asciz       LocalVariableTable;
22 const #12 = Asciz       this;
23 const #13 = Asciz       LTest;;
24 const #14 = Asciz       main;
25 const #15 = Asciz       ([Ljava/lang/String;)V;
26 const #16 = Method      #1.#9;  //  Test."<init>":()V
27 const #17 = Method      #1.#18; //  Test.initData:()V
28 const #18 = NameAndType #19:#6;//  initData:()V
29 const #19 = Asciz       initData;
30 const #20 = Asciz       args;
31 const #21 = Asciz       [Ljava/lang/String;;
32 const #22 = class       #23;    //  Test$A
33 const #23 = Asciz       Test$A;
34 const #24 = Method      #22.#25;        //  Test$A."<init>":(LTest;LTest$A;)V
35 const #25 = NameAndType #5:#26;//  "<init>":(LTest;LTest$A;)V
36 const #26 = Asciz       (LTest;LTest$A;)V;
37 const #27 = Method      #22.#28;        //  Test$A.access$1:(LTest$A;I)V
38 const #28 = NameAndType #29:#30;//  access$1:(LTest$A;I)V
39 const #29 = Asciz       access$1;
40 const #30 = Asciz       (LTest$A;I)V;
41 const #31 = class       #32;    //  Test$B
42 const #32 = Asciz       Test$B;
43 const #33 = Method      #31.#34;        //  Test$B."<init>":(LTest$B;)V
44 const #34 = NameAndType #5:#35;//  "<init>":(LTest$B;)V
45 const #35 = Asciz       (LTest$B;)V;
46 const #36 = Method      #31.#37;        //  Test$B.access$1:(LTest$B;I)V
47 const #37 = NameAndType #29:#38;//  access$1:(LTest$B;I)V
48 const #38 = Asciz       (LTest$B;I)V;
49 const #39 = Asciz       SourceFile;
50 const #40 = Asciz       Test.java;
51 const #41 = Asciz       InnerClasses;
52 const #42 = Asciz       A;
53 const #43 = Asciz       B;
54 
55 {
56 public Test();
57   Code:
58    Stack=1, Locals=1, Args_size=1
59    0:   aload_0
60    1:   invokespecial   #8; //Method java/lang/Object."<init>":()V
61    4:   return
62   LineNumberTable:
63    line 1: 0
64 
65   LocalVariableTable:
66    Start  Length  Slot  Name   Signature
67    0      5      0    this       LTest;
68 
69 
70 public static void main(java.lang.String[]);
71   Code:
72    Stack=2, Locals=1, Args_size=1
73    0:   new     #1; //class Test
74    3:   dup
75    4:   invokespecial   #16; //Method "<init>":()V
76    7:   invokespecial   #17; //Method initData:()V
77    10:  return
78   LineNumberTable:
79    line 5: 0
80    line 6: 10
81 
82   LocalVariableTable:
83    Start  Length  Slot  Name   Signature
84    0      11      0    args       [Ljava/lang/String;
85 
86 
87 }

InitDataメソッドスタックに入った後のコード解析から,まずAのメソッドが呼び出され,このメソッドは自動生成の2つのメソッドの1つである.Aを呼び出すコンストラクション関数(もう1つはであり、仮想マシンが.classを最初にロードするときに静的変数などを初期化するために使用される)は、その後、Aのprivate変数にアクセスし、オペレータのアクセスはconst#27=Method#22.28;//Test$A.access$1:(LTest$A;I)Vに変更されたが、#22を知ることはできない.28は意味を表すが、javapはすでに人間的に注釈を付けており、このセグメントがAを呼び出すaccessメソッドであることを示しているが、後の$1の意味はAの最初のprivate変数であることを示している.Bにおけるprivate変数へのアクセスはほぼ同じであり,これ以上は言わない.これで,外部クラスは内部クラスに隠されたaccess静的メソッドによってprivate変数にアクセスし,private修飾子の役割原則を破壊しない第2の質問に答えることができる.
 
还有,需要问一下JVMの部分のソースコード(JDKをダウンロードした后のディレクトリの下のsrc.zipはJDKの部分のソースコードで、これではありません)を送ることができて、以前sunの公式サイトでまだ见たことがあるようで、今oracleのウェブサイトの上で探し出せませんでした.