こんにちは、若いパダワン。この記事では、Java プログラマーが一見不可能に見える状況でのみ使用する力であるフォースについて説明します。Java の暗い側面は、Reflection API です。Java では、リフレクションは Java Reflection API を使用して実装されます。
私のパッケージ階層では、 MyClassの完全な名前は「reflection.MyClass」になります。クラス名を知る簡単な方法もあります (クラス名を文字列として返す)。
Java リフレクションとは何ですか?
インターネット上には、短く正確で人気のある定義があります。リフレクション (後期ラテン語のreflectionから - 引き返すという意味) は、プログラムの実行中にプログラムに関するデータを探索するメカニズムです。リフレクションを使用すると、フィールド、メソッド、クラス コンストラクターに関する情報を探索できます。リフレクションを使用すると、コンパイル時には存在しなかったが、実行時には使用可能になった型を操作できます。エラー情報を発行するためのリフレクションと論理的に一貫したモデルにより、正しい動的コードの作成が可能になります。言い換えれば、Java でリフレクションがどのように機能するかを理解すると、素晴らしい機会が数多く開かれることになります。文字通り、クラスとそのコンポーネントをやりくりすることができます。以下に、リフレクションで許可されるものの基本的なリストを示します。- オブジェクトのクラスを学習/決定します。
- クラスの修飾子、フィールド、メソッド、定数、コンストラクター、およびスーパークラスに関する情報を取得します。
- 実装されたインターフェイスにどのメソッドが属しているかを調べます。
- 実行時までクラス名が不明なクラスのインスタンスを作成します。
- オブジェクトのフィールドの値を名前で取得および設定します。
- オブジェクトのメソッドを名前で呼び出します。
MyClass
。
public class MyClass {
private int number;
private String name = "default";
// public MyClass(int number, String name) {
// this.number = number;
// this.name = name;
// }
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public void setName(String name) {
this.name = name;
}
private void printData(){
System.out.println(number + name);
}
}
ご覧のとおり、これは非常に基本的なクラスです。パラメーターを含むコンストラクターは意図的にコメントアウトされています。これについては後で説明します。クラスの内容を注意深く見ると、おそらくnameフィールドのゲッターが存在しないことに気づいたでしょう。nameフィールド自体はプライベートアクセス修飾子でマークされています。クラス自体の外部からはアクセスできません。つまり、その値を取得できません。「それで、何が問題なのですか?」あなたは言う。「ゲッターを追加するか、アクセス修飾子を変更します。」そして、あなたは正しいでしょう。MyClass
コンパイルされた AAR ライブラリ、または変更を加えることができない別のプライベート モジュール内にありました。実際には、このようなことは常に起こります。そして、不注意なプログラマーはゲッターを書き忘れただけです。今こそ反省を忘れない時期です!クラスのプライベート名フィールドにアクセスしてみましょうMyClass
。
public static void main(String[] args) {
MyClass myClass = new MyClass();
int number = myClass.getNumber();
String name = null; // No getter =(
System.out.println(number + name); // Output: 0null
try {
Field field = myClass.getClass().getDeclaredField("name");
field.setAccessible(true);
name = (String) field.get(myClass);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
System.out.println(number + name); // Output: 0default
}
何が起こったのか分析してみましょう。Java には、 という素晴らしいクラスがありますClass
。これは、実行可能な Java アプリケーションのクラスとインターフェイスを表します。Class
との関係についてはClassLoader
、この記事の主題ではないため説明しません。次に、このクラスのフィールドを取得するには、メソッドを呼び出す必要がありますgetFields()
。このメソッドは、このクラスのアクセス可能なフィールドをすべて返します。私たちのフィールドはprivateであるため、これは機能しません。そのため、メソッドを使用しますgetDeclaredFields()
。このメソッドはクラス フィールドの配列も返しますが、現在はプライベートフィールドと保護されたフィールドが含まれています。この場合、関心のあるフィールドの名前がわかっているので、getDeclaredField(String)
次のメソッドを使用できます。String
は目的のフィールドの名前です。 ノート: getFields()
getDeclaredFields()
親クラスのフィールドを返さないでください。素晴らしい。私たちの名前をField
参照するオブジェクトを取得しました。フィールドはpublicではないため、それを操作するためのアクセスを許可する必要があります。このメソッドを使用してさらに先に進みます。これで、名前フィールドは完全に制御できるようになりました。オブジェクトのメソッドを呼び出すことでその値を取得できます。ここで、はクラスのインスタンスです。型を変換し、値をname変数に割り当てます。name フィールドに新しい値を設定するためのセッターが見つからない場合は、 setメソッドを使用できます。 setAccessible(true)
Field
get(Object)
Object
MyClass
String
field.set(myClass, (String) "new value");
おめでとう!これでリフレクションの基本をマスターし、プライベートフィールドにアクセスできました。try/catch
ブロックと、処理される例外の種類 に注意してください。IDE は、それらの存在が必要であることを単独で通知しますが、名前を見れば、なぜここに存在するのかが明確にわかります。続けていきましょう!お気づきかもしれませんが、このMyClass
クラスにはクラス データに関する情報を表示するメソッドがすでにあります。
private void printData(){
System.out.println(number + name);
}
しかし、このプログラマーはここにも指紋を残しました。このメソッドにはプライベートアクセス修飾子があり、データを表示するには毎回独自のコードを記述する必要があります。なんて混乱だ。私たちの反省はどこへ行ったのでしょうか?次の関数を作成します。
public static void printData(Object myClass){
try {
Method method = myClass.getClass().getDeclaredMethod("printData");
method.setAccessible(true);
method.invoke(myClass);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
}
ここでの手順は、フィールドを取得する場合とほぼ同じです。目的のメソッドに名前でアクセスし、そのメソッドへのアクセスを許可します。そしてオブジェクト上でMethod
メソッドを呼び出しますinvoke(Object, Args)
。ここObject
で もクラスのインスタンスですMyClass
。Args
はメソッドの引数ですが、私たちの引数には引数がありません。次に、printData
関数を使用して情報を表示します。
public static void main(String[] args) {
MyClass myClass = new MyClass();
int number = myClass.getNumber();
String name = null; //?
printData(myClass); // Output: 0default
try {
Field field = myClass.getClass().getDeclaredField("name");
field.setAccessible(true);
field.set(myClass, (String) "new value");
name = (String) field.get(myClass);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
printData(myClass);// Output: 0new value
}
万歳!これで、クラスのプライベート メソッドにアクセスできるようになりました。しかし、メソッドに引数がある場合、コンストラクターがコメントアウトされているのはなぜでしょうか? すべては適切なタイミングで。冒頭の定義から、リフレクションを使用すると、実行時(プログラムの実行中) にクラスのインスタンスを作成できることがわかります。クラスのフルネームを使用してオブジェクトを作成できます。クラスの完全名は、そのパッケージのパスを含むクラス名です。
MyClass.class.getName()
Java リフレクションを使用してクラスのインスタンスを作成しましょう。
public static void main(String[] args) {
MyClass myClass = null;
try {
Class clazz = Class.forName(MyClass.class.getName());
myClass = (MyClass) clazz.newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
System.out.println(myClass); // Output: created object reflection.MyClass@60e53b93
}
Java アプリケーションの起動時に、すべてのクラスが JVM にロードされるわけではありません。コードがMyClass
クラスを参照しない場合、ClassLoader
JVM へのクラスのロードを担当する はクラスをロードしません。つまり、強制的ClassLoader
にロードして、変数の形式でクラスの説明を取得する必要がありますClass
。これが、メソッドがある理由ですforName(String)
。ここで、 はString
説明が必要なクラスの名前です。オブジェクトを取得した後Сlass
、メソッドを呼び出すと、その記述を使用して作成されたオブジェクトnewInstance()
が返されます。Object
残っているのは、このオブジェクトをMyClass
クラス。いいね!難しかったですが、理解できたと思います。これで、文字通り 1 行でクラスのインスタンスを作成できるようになりました。残念ながら、説明されているアプローチは、デフォルトのコンストラクター (パラメーターなし) でのみ機能します。パラメーターを指定してメソッドやコンストラクターを呼び出すにはどうすればよいでしょうか? コンストラクターのコメントを解除します。予想通り、newInstance()
デフォルトのコンストラクターが見つからず、機能しなくなりました。クラスのインスタンス化を書き直してみましょう。
public static void main(String[] args) {
MyClass myClass = null;
try {
Class clazz = Class.forName(MyClass.class.getName());
Class[] params = {int.class, String.class};
myClass = (MyClass) clazz.getConstructor(params).newInstance(1, "default2");
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
System.out.println(myClass);// Output: created object reflection.MyClass@60e53b93
}
このgetConstructors()
メソッドはクラス コンストラクターを取得するためにクラス定義で呼び出され、次にgetParameterTypes()
コンストラクターのパラメーターを取得するために呼び出される必要があります。
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
Class[] paramTypes = constructor.getParameterTypes();
for (Class paramType : paramTypes) {
System.out.print(paramType.getName() + " ");
}
System.out.println();
}
これにより、すべてのコンストラクターとそのパラメーターが取得されます。この例では、特定の既知のパラメーターを持つ特定のコンストラクターを参照しています。newInstance
このコンストラクターを呼び出すには、これらのパラメーターの値を渡すメソッドを使用します。invoke
を使用してメソッドを呼び出す場合も同様です。ここで疑問が生じます。リフレクションによるコンストラクターの呼び出しが役立つのはどのような場合ですか? 冒頭ですでに述べたように、最新の Java テクノロジーは Java Reflection API なしではやっていけません。たとえば、Dependency Injection (DI) は、アノテーションとメソッドおよびコンストラクターのリフレクションを組み合わせて、人気のあるDarerを形成します。Android開発用のライブラリです。この記事を読んだ後は、自信を持って Java Reflection API の知識を習得したと考えることができます。彼らはリフレクションを Java の暗い側面と呼んでいるわけではありません。これは OOP パラダイムを完全に破壊します。Java では、カプセル化により特定のプログラム コンポーネントへの他人のアクセスが隠蔽され、制限されます。private 修飾子を使用するときは、そのフィールドが存在するクラス内からのみアクセスされるようにすることを意図しています。そして、この原則に基づいてプログラムのその後のアーキテクチャを構築します。この記事では、リフレクションを使用してどこにでも強制的に進む方法を説明しました。 創造的なデザイン パターンSingletonは、アーキテクチャ上のソリューションとしての良い例です。基本的な考え方は、このパターンを実装するクラスは、プログラム全体の実行中にインスタンスを 1 つだけ持つということです。これは、プライベート アクセス修飾子をデフォルトのコンストラクターに追加することで実現されます。そして、プログラマがリフレクションを使用してそのようなクラスのインスタンスをさらに作成するのは非常に悪いことです。ところで、最近、同僚が非常に興味深い質問をしているのを聞きました。シングルトン パターンを実装するクラスは継承できますか? この場合、反省すら無力だということだろうか。記事に関するフィードバックと回答を以下のコメント欄に残して、そこでご自身の質問をしてください。
さらに読む: |
---|
GO TO FULL VERSION