Java プログラミングでは、private キーワードを使用してメンバーが変更された場合、メンバーが配置されているクラスとこのクラスのメソッドのみが使用でき、他のクラスはこのプライベート メンバーにアクセスできません。
private 修飾子の基本的な機能は上で説明しました。今日は、private 関数の失敗について学習します。
Javaの内部クラス
Java で内部クラスを使用したことのある人は多いと思います。Java では、クラス内のクラスを別のクラス内で定義できます。これは、ネストされたクラスとも呼ばれます。単純な内部クラスの実装は次のようにコピーできます。
クラスOuterClass {
クラスインナークラス{
}
}
今日の質問は Java 内部クラスに関するもので、この記事の研究に関連する内部クラスの知識の一部のみが含まれます。Java 内部クラスに関する具体的な記事は今後紹介されます。
初めての失敗?
プログラミングでよく使用されるシナリオは、内部クラスの外部クラスのプライベート メンバー変数またはメソッドにアクセスすることです。これは問題ありません。以下のコードとして実装します。
次のようにコードをコピーします。
パブリッククラスOuterClass{
private String language = "en";
プライベート文字列領域 = "US";
パブリック クラス InnerClass {
public void printOuterClassPrivateFields() {
文字列フィールド = "言語=" + 言語 + ";地域=" + 地域;
System.out.println(フィールド);
}
}
public static void main(String[] args) {
アウタークラスアウター = 新しいアウタークラス();
innerClass.InnerClass inner = inner.new InnerClass();
inner.printOuterClassPrivateFields();
}
}
これはなぜですか? private で変更されたメンバーは、そのメンバーが表すクラスからのみアクセスできるというのは本当ではないでしょうか?プライベートは本当に無効ですか?
コンパイラが問題を起こしているのでしょうか?
javap コマンドを使用して、生成された 2 つのクラス ファイルを表示してみましょう。
OuterClassの逆コンパイル結果のコピーコードは以下のとおりです。
15:30 $ javap -c アウタークラス
「OuterClass.java」からコンパイル
パブリッククラスOuterClass extends java.lang.Object{
パブリックOuterClass();
コード:
0: aload_0
1: invokespecial #11; //メソッド java/lang/Object."<init>":()V;
4: aload_0
5: ldc #13; //文字列 en
7: putfield #15; //フィールド言語:Ljava/lang/String;
10: aload_0
11: ldc #17; //文字列 US
13: putfield #19; //フィールド領域:Ljava/lang/String;
16: 戻る
public static void main(java.lang.String[]);
コード:
0: 新しい #1 //クラス アウタークラス;
3: ダップ
4: invokespecial #27; //メソッド "<init>":()V;
7:astore_1
8: new #28; //クラスOuterClass$InnerClass;
11:ダップ
12: aload_1
13:ダップ
14: invokevirtual #30; //メソッド java/lang/Object.getClass:()Ljava/lang/Class;
17:ポップ
18: invokespecial #34; //メソッドOuterClass$InnerClass."<init>":(LOuterClass;)V
21:astore_2
22: ロード_2
23: invokevirtual #37; //メソッドOuterClass$InnerClass.printOuterClassPrivateFields:()V
26: 戻る
static java.lang.String access$0(OuterClass);
コード:
0: aload_0
1: getfield #15; //フィールド言語:Ljava/lang/String;
4:リターン
静的 java.lang.String access$1(OuterClass);
コード:
0: aload_0
1: getfield #19; //フィールド領域:Ljava/lang/String;
4:リターン
}
はぁ?いいえ、OuterClass ではこれら 2 つのメソッドを定義していません
static java.lang.String access$0(OuterClass);
コード:
0: aload_0
1: getfield #15; //フィールド言語:Ljava/lang/String;
4:リターン
静的 java.lang.String access$1(OuterClass);
コード:
0: aload_0
1: getfield #19; //フィールド領域:Ljava/lang/String;
4:リターン
}
与えられたコメントから判断すると、access$0 は、outerClass の language 属性を返し、access$1 は、outerClass のregion 属性を返します。どちらのメソッドも、OuterClass のインスタンスをパラメーターとして受け入れます。これら 2 つのメソッドはなぜ生成され、何を行うのでしょうか?内部クラスの逆コンパイル結果を見てみましょう。そうすればわかります。
InnerClass$InnerClass の逆コンパイル結果
次のようにコードをコピーします。
15:37 $ javap -c アウタークラス/$インナークラス
「OuterClass.java」からコンパイル
パブリッククラスOuterClass$InnerClass extends java.lang.Object{
最終のアウタークラス this$0;
パブリックOuterClass$InnerClass(OuterClass);
コード:
0: aload_0
1: aload_1
2: putfield #10; //フィールド this$0:LOuterClass;
5: aload_0
6: invokespecial #12; //メソッド java/lang/Object."<init>":()V;
9: 戻る
public void printOuterClassPrivateFields();
コード:
0: 新しい #20; //クラス java/lang/StringBuilder
3: ダップ
4: ldc #22; //文字列言語=
6: invokespecial #24; //メソッド java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
9: aload_0
10: getfield #10; //フィールド this$0:LOuterClass;
13: invokestatic #27; //メソッドOuterClass.access$0:(LOuterClass;)Ljava/lang/String;
16: invokevirtual #33; //メソッド java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc #37; //文字列 ;region=
21: invokevirtual #33; //メソッド java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: aload_0
25: getfield #10; //フィールド this$0:LOuterClass;
28: invokestatic #39; //メソッドOuterClass.access$1:(LOuterClass;)Ljava/lang/String;
31: invokevirtual #33; //メソッド java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
34: invokevirtual #42; //メソッド java/lang/StringBuilder.toString:()Ljava/lang/String;
37: アストア_1
38: getstatic #46; //フィールド java/lang/System.out:Ljava/io/PrintStream;
41: ロード_1
42: invokevirtual #52; //メソッド java/io/PrintStream.println:(Ljava/lang/String;)V
45: 戻る
}
次のコードは、access$0 のコードを呼び出します。その目的は、OuterClass の言語プライベート属性を取得することです。
次のようにコードをコピーします。
13: invokestatic #27; //メソッドOuterClass.access$0:(LOuterClass;)Ljava/lang/String;
次のコードは、access$1 のコードを呼び出します。その目的は、OutherClass の領域プライベート属性を取得することです。
次のようにコードをコピーします。
28: invokestatic #39; //メソッドOuterClass.access$1:(LOuterClass;)Ljava/lang/String;
注: 内部クラスが構築されると、外部クラスの参照が渡され、内部クラスの属性として使用されるため、内部クラスはその外部クラスへの参照を保持します。
this$0 は、内部クラスが保持する外部クラス参照であり、コンストラクター メソッドを通じて渡され、割り当てられます。
次のようにコードをコピーします。
最終のアウタークラス this$0;
パブリックOuterClass$InnerClass(OuterClass);
コード:
0: aload_0
1: aload_1
2: putfield #10; //フィールド this$0:LOuterClass;
5: aload_0
6: invokespecial #12; //メソッド java/lang/Object."<init>":()V;
9: 戻る
まとめ
private のこの部分は無効であるように見えますが、実際には無効ではありません。内部クラスが外部クラスのプライベート プロパティを呼び出すとき、その実際の実行はコンパイラによって生成されたプロパティの静的メソッドを呼び出すことであるためです (つまり、アクセス$0、access$1 など) を使用してこれらの属性値を取得します。これはすべてコンパイラによる特別な処理です。
今回はうまくいきませんか?
上記の記述方法が非常に一般的に使用されている場合、この記述方法はほとんど使用されませんが、実行できます。
次のようにコードをコピーします。
パブリック クラス AnotherOuterClass {
public static void main(String[] args) {
InnerClass inner = new AnotherOuterClass().new InnerClass();
System.out.println("InnerClass Filed = " + inner.x);
}
クラスインナークラス {
プライベート int x = 10;
}
}
上記と同様に、javapを使用して逆コンパイルして見てください。今回は、InnerClass の結果を見てみましょう。コードは次のとおりです。
16:03 $ javap -c AnotherOuterClass/$InnerClass
「AnotherOuterClass.java」からコンパイル
class AnotherOuterClass$InnerClass extends java.lang.Object{
最終的な AnotherOuterClass this$0;
AnotherOuterClass$InnerClass(AnotherOuterClass);
コード:
0: aload_0
1: aload_1
2: putfield #12; //フィールド this$0:LANotherOuterClass;
5: aload_0
6: invokespecial #14; //メソッド java/lang/Object."<init>":()V;
9: aload_0
10:バイプッシュ10
12: putfield #17 //フィールド x:I;
15: 戻る
static int access$0(AnotherOuterClass$InnerClass);
コード:
0: aload_0
1: getfield #17 //フィールド x:I;
4:ireturn
}
この問題は再び発生し、コンパイラは、x の値を取得するために一度プライベート属性を取得するバックドア メソッド access$0 を自動的に生成しました。
AnotherOuterClass.classの逆コンパイル結果のコピーコードは以下のとおりです。
16:08 $ javap -c AnotherOuterClass
「AnotherOuterClass.java」からコンパイル
パブリック クラス AnotherOuterClass extends java.lang.Object{
public AnotherOuterClass();
コード:
0: aload_0
1: invokespecial #8; //メソッド java/lang/Object."<init>":()V;
4: 戻る
public static void main(java.lang.String[]);
コード:
0: new #16; //クラス AnotherOuterClass$InnerClass
3: ダップ
4: 新しい #1 //クラス AnotherOuterClass
7:ダップ
8: invokespecial #18; //メソッド "<init>":()V
11:ダップ
12: invokevirtual #19; //メソッド java/lang/Object.getClass:()Ljava/lang/Class;
15:ポップ
16: invokespecial #23; //メソッド AnotherOuterClass$InnerClass."<init>":(LAnotherOuterClass;)V
19:astore_1
20: getstatic #26; //フィールド java/lang/System.out:Ljava/io/PrintStream;
23: 新しい #32; //クラス java/lang/StringBuilder
26:ダップ
27: ldc #34; // 文字列 InnerClass Filed =
29: invokespecial #36; //メソッド java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
32: ロード_1
33: invokestatic #39; //メソッド AnotherOuterClass$InnerClass.access$0:(LAnotherOuterClass$InnerClass;)I
36: invokevirtual #43; //メソッド java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
39: invokevirtual #47; //メソッド java/lang/StringBuilder.toString:()Ljava/lang/String;
42: invokevirtual #51; //メソッド java/io/PrintStream.println:(Ljava/lang/String;)V
45: 戻る
}
この呼び出しは、内部クラスのインスタンスを通じてプライベート プロパティ x を取得する外部クラスの操作です。次のようにコードをコピーします。
33: invokestatic #39; //メソッド AnotherOuterClass$InnerClass.access$0:(LAnotherOuterClass$InnerClass;)I
別のまとめ
その中で、Java の公式ドキュメントには次のようなコードが記載されています。
メンバーまたはコンストラクターがプライベートと宣言されている場合、メンバーまたはコンストラクターの宣言を囲む最上位クラス (§7.6) の本体内でアクセスが許可される。
これは、(内部クラスの) メンバーとコンストラクターがプライベート修飾子に設定されている場合、その外部クラスである場合にのみアクセスが許可されることを意味します。
内部クラスのプライベートメンバーが外部者によってアクセスされるのを防ぐ方法
上記の 2 つの部分を読むと、内部クラスのプライベート メンバーが外部クラスからアクセスされるのは非常に難しいと感じられると思います。終わり。それは、匿名の内部クラスを使用することです。
mRunnable オブジェクトの型は Runnable であり、匿名内部クラス (通常は取得できない) の型ではなく、Runnble には x 属性がないため、mRunnable.x は許可されません。
次のようにコードをコピーします。
パブリック クラス PrivateToOuter {
実行可能 mRunnable = new Runnable(){
プライベート int x=10;
@オーバーライド
public void run() {
System.out.println(x);
}
};
public static void main(String[] args){
PrivateToOuter p = new PrivateToOuter();
//System.out.println("anonymous class private filed= "+ p.mRunnable.x); //許可されません
p.mRunnable.run(); // 許可されます
}
}
最終的なまとめ
この記事では、private は表面的には無効であるように見えますが、実際はそうではなく、呼び出し時に間接メソッドを通じてプライベート プロパティが取得されます。
Java の内部クラスは構築時に外部クラスのアプリケーションを保持しますが、C++ はそうではありません。