JavaRush /Java Blog /Random-JA /リフレクション API。反射。Javaのダークサイド
Oleksandr Klymenko
レベル 13
Харків

リフレクション API。反射。Javaのダークサイド

Random-JA グループに公開済み
こんにちは、若いパダワン。この記事では、Java プログラマーが一見絶望的な状況でのみ使用する力であるフォースについて説明します。つまり、Java の暗い側面は -Reflection API
リフレクション API。 反射。 Java のダークサイド - 1
Java でのリフレクションは、Java Reflection API を使用して行われます。この反射は何でしょうか?インターネット上でも人気のある短くて正確な定義があります。 リフレクション (後期ラテン語の反射 - 遡る) は、プログラムの実行中にプログラムに関するデータを研究するためのメカニズムです。リフレクションを使用すると、フィールド、メソッド、クラス コンストラクターに関する情報を調べることができます。リフレクション メカニズム自体により、コンパイル中には存在しなかったが、プログラムの実行中に出現した型を処理できます。エラーを報告するためのリフレクションと論理的に一貫したモデルの存在により、正しい動的コードを作成することが可能になります。言い換えれば、Java でリフレクションがどのように機能するかを理解すると、多くの素晴らしい機会が開かれます。文字通り、クラスとそのコンポーネントをやりくりすることができます。
リフレクション API。 反射。 Java のダークサイド - 2
以下に、リフレクションで許可されるものの基本的なリストを示します。
  • オブジェクトのクラスを調べて決定します。
  • クラス修飾子、フィールド、メソッド、定数、コンストラクター、スーパークラスに関する情報を取得します。
  • 実装されたインターフェイスにどのメソッドが属しているかを調べます。
  • クラスのインスタンスを作成します。クラスの名前は、プログラムが実行されるまで不明です。
  • オブジェクトフィールドの値を名前で取得および設定します。
  • オブジェクトのメソッドを名前で呼び出します。
リフレクションは、ほとんどすべての最新の Java テクノロジーで使用されています。プラットフォームとしての Java が反省なしにこれほど大規模な採用を達成できたのかどうかを想像するのは困難です。おそらくできなかったでしょう。反射の一般的な理論的考え方については理解できたので、実際の応用に取り掛かりましょう。Reflection API のすべてのメソッドを学習するわけではなく、実際に実際に遭遇するメソッドのみを学習します。リフレクション メカニズムにはクラスの操作が含まれるため、単純なクラスを作成します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);
   }
}
ご覧のとおり、これは最も一般的なクラスです。パラメータを持つコンストラクタは理由によりコメントアウトされています。これについては後で説明します。クラスの内容をよく見てみると、おそらく、 のgetter「a」が存在しないことがわかりますname。フィールド自体はnameアクセス修飾子でマークされているためprivate、クラス自体の外部からフィールドにアクセスすることはできず、=>その値を取得することもできません。"だから問題は何ですか?- あなたは言う。「getterアクセス修飾子を追加または変更します。」あなたは正しいでしょうが、MyClassそれがコンパイル済みの aar ライブラリ内、または編集アクセス権のない別の閉じられたモジュール内にある場合はどうなるでしょうか。実際には、これは非常に頻繁に発生します。そして、注意力のないプログラマの中には、単に を書き忘れた人もいますgetter。反省について思い出す時が来ました!classprivateフィールドにアクセスしてみましょう。 nameMyClass
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()。このメソッドはクラス フィールドの配列も返しますが、現在は と の両方が返されprivateますprotected。この状況では、関心のあるフィールドの名前がわかっているので、メソッドを使用できますgetDeclaredField(String)。ここで、Stringは目的のフィールドの名前です。 注記: getFields()getDeclaredFields()親クラスのフィールドを返さないでください。Field すばらしいです。へのリンクを持つオブジェクトを受け取りましたname。なぜなら フィールドはпубличным(パブリック) ではなかったので、それを操作するにはアクセス権を与える必要があります。この方法により、setAccessible(true)作業を続けることができます。これで、フィールドはname完全に私たちの管理下になりました。get(Object)オブジェクトを呼び出すことでその値を取得できますField。ここで、ObjectはクラスのインスタンスですMyClass。それをキャストしStringて変数に割り当てますname。突然 'a がなくなった場合はsetter、メソッドを使用して name フィールドに新しい値を設定できますset
field.set(myClass, (String) "new value");
おめでとう!あなたはリフレクションの基本メカニズムをマスターし、privateフィールドにアクセスすることができました。 ブロックtry/catchと処理される例外の種類に注意してください。IDE 自体はそれらが必須であることを示しますが、その名前によって、なぜここに存在するのかが明確になります。どうぞ!お気づきかもしれませんが、私たちのメソッドにMyClassはクラス データに関する情報を表示するメソッドがすでにあります。
private void printData(){
       System.out.println(number + name);
   }
しかし、このプログラマーはここにも伝説を残しました。このメソッドはアクセス修飾子の下にあるためprivate、毎回自分で出力コードを記述する必要がありました。順序が間違っています。反省点はどこにあるでしょうか... 次の関数を書いてみましょう。
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(Оbject, Args)ОbjectもクラスのインスタンスになりますMyClass Args- メソッドの引数 - 私たちのものには何もありません。次に、関数を使用して情報を表示しますprintData
public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; //?
   printData(myClass); // outout 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
}
さあ、これでクラスのプライベート メソッドにアクセスできるようになりました。しかし、メソッドにまだ引数がある場合はどうなるでしょうか。なぜコメントアウトされたコンストラクターがあるのでしょうか? 何事にも潮時というものがあります。runtime冒頭の定義から、リフレクションを使用すると (プログラムの実行中に)モードでクラスのインスタンスを作成できることがわかります。クラスのオブジェクトは、そのクラスの完全修飾名によって作成できます。完全修飾クラス名は、 で指定されたクラスの名前ですpackage
リフレクション API。 反射。 Java のダークサイド - 3
私の階層ではpackageフルネームはMyClass「 」になりますreflection.MyClass。簡単な方法でクラス名を見つけることもできます (クラス名が文字列として返されます)。
MyClass.class.getName()
リフレクションを使用してクラスのインスタンスを作成しましょう。
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、クラスを JVM にロードする担当者、つまり はClassLoaderクラスをそこにロードすることはありません。ClassLoaderしたがって、強制的にロードして、クラスの記述を type の変数の形式で受け取る必要があります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 テクノロジーは Reflection API なしでは機能しません。たとえば、DI (Dependency Injection) では、アノテーションとメソッドおよびコンストラクターのリフレクションが組み合わされて、Android 開発で人気のある Dagger ライブラリが形成されます。この記事を読めば、Reflection API のメカニズムについて自信を持って理解できるようになります。リフレクションが Java のダークサイドと呼ばれるのは当然のことです。これは OOP パラダイムを完全に破壊します。Java では、カプセル化は、一部のプログラム コンポーネントを隠し、他のプログラム コンポーネントへのアクセスを制限する役割を果たします。private 修飾子を使用すると、このフィールドへのアクセスは、このフィールドが存在するクラス内のみになることを意味し、これに基づいてプログラムのさらなるアーキテクチャを構築します。この記事では、リフレクションを使用してどこにでも到達できる方法について説明しました。アーキテクチャ ソリューションの形での良い例は、ジェネレーティブ デザイン パターンですSingleton。その主な考え方は、プログラムの動作全体を通じて、このテンプレートを実装するクラスのコピーは 1 つだけであるべきであるということです。これを行うには、コンストラクターのデフォルトのアクセス修飾子をプライベートに設定します。そして、プログラマーが自分自身の反省を持ってそのようなクラスを作成した場合、それは非常に悪いことになります。ところで、最近従業員から聞いた非常に興味深い質問があります。それは、テンプレートを実装するクラスにSingleton継承者を持つことができるのかということです。この場合、反射さえも無力である可能性はありますか?記事に対するフィードバックと回答をコメント欄に書いたり、質問したりしてください。Reflection API の真の能力は、ランタイム アノテーションと組み合わせることで発揮されます。これについては、おそらく Java の暗い側面に関する将来の記事で説明することになります。ご清聴ありがとうございました!
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION