CodeGym /Java Blog /ランダム /反省の例
John Squirrels
レベル 41
San Francisco

反省の例

ランダム グループに公開済み
あなたも普段の生活の中で「反省」という概念に出会ったことがあるかもしれません。この言葉は通常、自分自身を研究するプロセスを指します。プログラミングでも同様の意味があり、プログラムに関するデータを分析し、プログラムの実行中にプログラムの構造や動作を変更するメカニズムです。 反省の例 - 1 ここで重要なのは、これをコンパイル時ではなく実行時に行うということです。しかし、なぜ実行時にコードを調べるのでしょうか? 結局のところ、あなたはすでにコードを読むことができます :/ リフレクションの概念がすぐには理解できないのには理由があります。この時点までは、どのクラスを扱っているかが常にわかっていました。たとえば、次のようなCatクラスを作成できます。

package learn.codegym;

public class Cat {

   private String name;
   private int age;

   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   public void sayMeow() {

       System.out.println("Meow!");
   }

   public void jump() {

       System.out.println("Jump!");
   }

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public int getAge() {
       return age;
   }

   public void setAge(int age) {
       this.age = age;
   }

@Override
public String toString() {
   return "Cat{" +
           "name='" + name + '\'' +
           ", age=" + age +
           '}';
}

}
あなたはそれについてすべてを知っており、それが持つフィールドとメソッドを見ることができます。突然、他の動物クラスをプログラムに導入する必要があるとします。Animalおそらく、便宜上、親クラスを使用してクラス継承構造を作成することができます。Animal以前に、オブジェクト (親クラスのインスタンス) を渡すことができる動物病院を表すクラスも作成しました。プログラムは、犬か猫かに基づいて動物を適切に扱いました。これらは最も単純なタスクではありませんが、プログラムはコンパイル時にクラスに関する必要な情報をすべて学習できます。Catしたがって、動物病院クラスのメソッドにオブジェクトを渡すと、main()この方法では、プログラムはそれが犬ではなく猫であることをすでに知っています。ここで、別のタスクに直面していると想像してみましょう。私たちの目標は、コード アナライザーを作成することです。CodeAnalyzer単一のメソッドを持つクラスを作成する必要がありますvoid analyzeObject(Object o)。このメソッドは次のことを行う必要があります。
  • 渡されたオブジェクトのクラスを特定し、コンソールにクラス名を表示します。
  • プライベートフィールドを含む、渡されたクラスのすべてのフィールドの名前を決定し、コンソールに表示します。
  • プライベートメソッドを含む、渡されたクラスのすべてのメソッドの名前を決定し、コンソールに表示します。
次のようになります。

public class CodeAnalyzer {

   public static void analyzeClass(Object o) {
      
       // Print the name of the class of object o
       // Print the names of all variables of this class
       // Print the names of all methods of this class
   }
  
}
これで、このタスクが以前に解決した他のタスクとどのように異なるかが明確にわかります。私たちの現在の目標の難しさは、私たちもプログラムも正確に何が渡されるのかわからないという事実にあります。analyzeClass()方法。このようなプログラムを作成すると、他のプログラマがそれを使用し始め、標準 Java クラスや作成した他のクラスなど、あらゆるものをこのメソッドに渡す可能性があります。渡されたクラスには、任意の数の変数とメソッドを含めることができます。言い換えれば、私たち (そして私たちのプログラム) は、どのクラスを扱うことになるのか全く分かりません。それでも、このタスクを完了する必要があります。ここで、標準の Java Reflection API が役に立ちます。Reflection API は、この言語の強力なツールです。Oracle の公式ドキュメントでは、このメカニズムは、自分が何をしているのかを理解している経験豊富なプログラマのみが使用することを推奨しています。なぜこのような警告を事前に出すのかはすぐに理解できるでしょう :) 以下は、Reflection API で実行できることのリストです。
  1. オブジェクトのクラスを識別/決定します。
  2. クラス修飾子、フィールド、メソッド、定数、コンストラクター、およびスーパークラスに関する情報を取得します。
  3. 実装されたインターフェイスにどのメソッドが属しているかを調べます。
  4. プログラムが実行されるまでクラス名が不明なクラスのインスタンスを作成します。
  5. インスタンス フィールドの値を名前で取得および設定します。
  6. インスタンス メソッドを名前で呼び出します。
印象的なリストですね。:) ノート:リフレクション メカニズムは、コード アナライザーに渡すオブジェクトの種類に関係なく、これらすべてを「オンザフライ」で実行できます。いくつかの例を見て、Reflection API の機能を調べてみましょう。

オブジェクトのクラスを識別/決定する方法

基本から始めましょう。Java リフレクション エンジンへのエントリ ポイントはClassクラスです。はい、とても面白いように見えますが、それがリフレクションです :) このClassクラスを使用して、まずメソッドに渡されるオブジェクトのクラスを決定します。これをやってみましょう:

import learn.codegym.Cat;

public class CodeAnalyzer {

   public static void analyzeClass(Object o) {
       Class clazz = o.getClass();
       System.out.println(clazz);
   }

   public static void main(String[] args) {

       analyzeClass(new Cat("Fluffy", 6));
   }
}
コンソール出力:

class learn.codegym.Cat
2つのことに注意してください。Catまず、クラスを意図的に別のパッケージに入れますlearn.codegymgetClass()これで、メソッドがクラスの完全名を返すことがわかります。次に、変数に という名前を付けましたclazz。それは少し奇妙に見えます。これを「クラス」と呼ぶのは理にかなっていますが、「クラス」は Java の予約語です。コンパイラは、変数をそのように呼び出すことを許可しません。何とかしてそれを回避する必要がありました:) スタートとしては悪くありません! その機能リストには他に何があったでしょうか?

クラス修飾子、フィールド、メソッド、定数、コンストラクター、およびスーパークラスに関する情報を取得する方法。

今、物事はさらに面白くなってきています!現在のクラスには定数も親クラスもありません。それらを追加して全体像を作成しましょう。最も単純なAnimal親クラスを作成します。

package learn.codegym;
public class Animal {

   private String name;
   private int age;
}
そしてCatクラスを継承させAnimal、定数を 1 つ追加します。

package learn.codegym;

public class Cat extends Animal {

   private static final String ANIMAL_FAMILY = "Feline family";

   private String name;
   private int age;

   // ...the rest of the class
}
これで全体像が見えてきました!リフレクションで何ができるか見てみましょう:)

import learn.codegym.Cat;

import java.util.Arrays;

public class CodeAnalyzer {

   public static void analyzeClass(Object o) {
       Class clazz = o.getClass();
       System.out.println("Class name: " + clazz);
       System.out.println("Class fields: " + Arrays.toString(clazz.getDeclaredFields()));
       System.out.println("Parent class: " + clazz.getSuperclass());
       System.out.println("Class methods: " + Arrays.toString(clazz.getDeclaredMethods()));
       System.out.println("Class constructors: " + Arrays.toString(clazz.getConstructors()));
   }

   public static void main(String[] args) {

       analyzeClass(new Cat("Fluffy", 6));
   }
}
コンソールに表示される内容は次のとおりです。

Class name:  class learn.codegym.Cat 
Class fields: [private static final java.lang.String learn.codegym.Cat.ANIMAL_FAMILY, private java.lang.String learn.codegym.Cat.name, private int learn.codegym.Cat.age] 
Parent class: class learn.codegym.Animal 
Class methods: [public java.lang.String learn.codegym.Cat.getName(), public void learn.codegym.Cat.setName(java.lang.String), public void learn.codegym.Cat.sayMeow(), public void learn.codegym.Cat.setAge(int), public void learn.codegym.Cat.jump(), public int learn.codegym.Cat.getAge()] 
Class constructors: [public learn.codegym.Cat(java.lang.String, int)]
私たちが入手できた詳細なクラス情報をご覧ください。公開情報だけでなく個人情報も! ノート: private変数もリストに表示されます。クラスの「分析」は基本的に完了したと考えることができます。私たちはこのanalyzeObject()メソッドを使用して、できる限りすべてを学習しています。しかし、これが私たちが反省でできるすべてではありません。単なる観察にとどまらず、行動に移していきます。:)

プログラムが実行されるまでクラス名が不明なクラスのインスタンスを作成する方法。

デフォルトのコンストラクターから始めましょう。私たちのCatクラスにはまだないので、追加しましょう。

public Cat() {
  
}
Catリフレクション (createCat()メソッド) を使用してオブジェクト を作成するコードは次のとおりです。

import learn.codegym.Cat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {

   public static Cat createCat() throws IOException, IllegalAccessException, InstantiationException, ClassNotFoundException {

       BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
       String className = reader.readLine();

       Class clazz = Class.forName(className);
       Cat cat = (Cat) clazz.newInstance();

       return cat;
   }

public static Object createObject() throws Exception {

   BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
   String className = reader.readLine();

   Class clazz = Class.forName(className);
   Object result = clazz.newInstance();

   return result;
}

   public static void main(String[] args) throws IOException, IllegalAccessException, ClassNotFoundException, InstantiationException {
       System.out.println(createCat());
   }
}
コンソール入力:

learn.codegym.Cat
コンソール出力:

Cat{name='null', age=0}
これはエラーではありません。クラスのメソッドでnameと の値ageを出力するコードを記述したため、 と の値がコンソールに表示されます。ここでは、コンソールからオブジェクトを作成するクラスの名前を読み取ります。プログラムは、オブジェクトが作成されるクラスの名前を認識します。 簡潔にするために、例自体よりも多くのスペースを占める適切な例外処理コードを省略しました。もちろん、実際のプログラムでは、名前が間違って入力された場合などに対処する必要があります。デフォルトのコンストラクターは非常にシンプルなので、ご覧のとおり、これを使用してクラスのインスタンスを作成するのは簡単です:) 、このクラスの新しいオブジェクトを作成します。それは別の問題ですtoString()Cat反省の例 - 3newInstance()Catコンストラクターは引数を入力として受け取ります。クラスのデフォルトのコンストラクターを削除して、コードを再度実行してみましょう。

null
java.lang.InstantiationException: learn.codegym.Cat 
at java.lang.Class.newInstance(Class.java:427)
何か問題が発生しました! デフォルトのコンストラクターを使用してオブジェクトを作成するメソッドを呼び出したため、エラーが発生しました。しかし、現在そのようなコンストラクターはありません。したがってnewInstance()、メソッドが実行されると、リフレクション メカニズムは 2 つのパラメーターを持つ古いコンストラクターを使用します。

public Cat(String name, int age) {
   this.name = name;
   this.age = age;
}
しかし、パラメータについてはまったく忘れたかのように、何もしませんでした。リフレクションを使用してコンストラクターに引数を渡すには、少しの「創造性」が必要です。

import learn.codegym.Cat;

import java.lang.reflect.InvocationTargetException;

public class Main {

   public static Cat createCat()  {

       Class clazz = null;
       Cat cat = null;

       try {
           clazz = Class.forName("learn.codegym.Cat");
           Class[] catClassParams = {String.class, int.class};
           cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Fluffy", 6);
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } catch (InstantiationException e) {
           e.printStackTrace();
       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (NoSuchMethodException e) {
           e.printStackTrace();
       } catch (InvocationTargetException e) {
           e.printStackTrace();
       }

       return cat;
   }

   public static void main(String[] args) {
       System.out.println(createCat());
   }
}
コンソール出力:

Cat{name='Fluffy', age=6}
プログラムで何が起こっているのかを詳しく見てみましょう。オブジェクトの配列を作成しましたClass

Class[] catClassParams = {String.class, int.class};
Stringこれらは、コンストラクター (パラメーターとintパラメーターのみを持つ) のパラメーターに対応します。それらをメソッドに渡しclazz.getConstructor()、目的のコンストラクターにアクセスします。その後、必要newInstance()な引数を指定してメソッドを呼び出すだけです。オブジェクトを目的の型に明示的にキャストすることを忘れないでくださいCat

cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Fluffy", 6);
これでオブジェクトが正常に作成されました。コンソール出力:

Cat{name='Fluffy', age=6}
右に進みます:)

インスタンスフィールドの値を名前で取得および設定する方法。

別のプログラマが作成したクラスを使用していると想像してください。さらに、編集することもできません。たとえば、JAR にパッケージ化された既製のクラス ライブラリです。クラスのコードを読み取ることはできますが、変更することはできません。このライブラリ内のクラスの 1 つ (これを古いクラスとしましょうCat) を作成したプログラマが、設計が完成する前夜に十分な睡眠が取れず、フィールドのゲッターとセッターを削除したとしますage。このクラスがあなたのところにやって来ました。Catプログラムにはオブジェクトだけが必要なので、すべてのニーズを満たします。しかし、彼らにはフィールドが必要ですage。これは問題です。フィールドに到達できないのです。private修飾子、ゲッターとセッターはクラスを作成した睡眠不足の開発者によって削除されました :/ そうですね、この状況ではリフレクションが役に立ちます。クラスのコードにアクセスできるCatので、少なくともそのクラスにどのようなフィールドがあり、それらが何と呼ばれているかを知ることができます。この情報があれば、問題を解決できます。

import learn.codegym.Cat;

import java.lang.reflect.Field;

public class Main {

   public static Cat createCat()  {

       Class clazz = null;
       Cat cat = null;
       try {
           clazz = Class.forName("learn.codegym.Cat");
           cat = (Cat) clazz.newInstance();

           // We got lucky with the name field, since it has a setter
           cat.setName("Fluffy");

           Field age = clazz.getDeclaredField("age");
          
           age.setAccessible(true);

           age.set(cat, 6);

       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (InstantiationException e) {
           e.printStackTrace();
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } catch (NoSuchFieldException e) {
           e.printStackTrace();
       }

       return cat;
   }

   public static void main(String[] args) {
       System.out.println(createCat());
   }
}
コメントに記載されているように、nameクラス開発者がセッターを提供しているため、フィールドに関するすべての作業は簡単です。デフォルトのコンストラクターからオブジェクトを作成する方法はすでにご存知でしょう。newInstance()このための が用意されています。ただし、2 番目のフィールドを少しいじる必要があります。ここで何が起こっているのか見てみましょう:)

Field age = clazz.getDeclaredField("age");
ここでは、オブジェクトを使用して、メソッド経由でフィールドClass clazzにアクセスします。これにより、年齢フィールドをオブジェクトとして取得できます。しかし、これでは十分ではありません。単にフィールドに値を割り当てることはできないからです。これを行うには、次のメソッドを使用してフィールドにアクセスできるようにする必要があります。 agegetDeclaredField()Field ageprivatesetAccessible()

age.setAccessible(true);
これをフィールドに行うと、値を割り当てることができます。

age.set(cat, 6);
ご覧のとおり、このField ageオブジェクトには、int 値とフィールドが割り当てられるオブジェクトを渡す、一種のインサイドアウト セッターがあります。メソッドを実行するmain()と、次のことがわかります。

Cat{name='Fluffy', age=6}
素晴らしい!やった!:) 他に何ができるか見てみましょう...

インスタンス メソッドを名前で呼び出す方法。

前の例の状況を少し変えてみましょう。Catクラス開発者がゲッターとセッターを間違えなかったとしましょう。その点に関しては大丈夫です。さて、問題は異なります。絶対に必要なメソッドがありますが、開発者はそれを非公開にしました。

private void sayMeow() {

   System.out.println("Meow!");
}
これは、プログラム内でオブジェクトを作成した場合Cat、それらのメソッドを呼び出すことができないことを意味しますsayMeow()。鳴かない猫を飼うつもりですか?それは奇妙です :/ これを修正するにはどうすればよいでしょうか? もう一度、Reflection API が役に立ちます。必要なメソッドの名前はわかっています。それ以外はすべて技術的なものです。

import learn.codegym.Cat;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {

   public static void invokeSayMeowMethod()  {

       Class clazz = null;
       Cat cat = null;
       try {

           cat = new Cat("Fluffy", 6);
          
           clazz = Class.forName(Cat.class.getName());
          
           Method sayMeow = clazz.getDeclaredMethod("sayMeow");
          
           sayMeow.setAccessible(true);
          
           sayMeow.invoke(cat);
          
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } catch (NoSuchMethodException e) {
           e.printStackTrace();
       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (InvocationTargetException e) {
           e.printStackTrace();
       }
   }

   public static void main(String[] args) {
       invokeSayMeowMethod();
   }
}
ここでは、プライベート フィールドにアクセスするときに行ったのとほぼ同じことを行います。まず、必要なメソッドを取得します。これはMethodオブジェクトにカプセル化されています。

Method sayMeow = clazz.getDeclaredMethod("sayMeow");
このgetDeclaredMethod()メソッドを使用すると、プライベート メソッドにアクセスできます。次に、メソッドを呼び出し可能にします。

sayMeow.setAccessible(true);
最後に、目的のオブジェクトでメソッドを呼び出します。

sayMeow.invoke(cat);
ここで、メソッド呼び出しは「コールバック」のように見えます。私たちはオブジェクトを目的のメソッド ( ) に指すためにピリオドを使用することに慣れていますcat.sayMeow()が、リフレクションを使用する場合は、呼び出したいオブジェクトをメソッドに渡します。その方法。コンソールには何が表示されますか?

Meow!
すべてうまくいきました!:) これで、Java のリフレクション メカニズムがもたらす広大な可能性がわかりました。困難で予期しない状況 (閉鎖されたライブラリのクラスを使用した例など) では、これは非常に役立ちます。しかし、他の大国と同様に、それには大きな責任が伴います。リフレクションの欠点については、Oracle Web サイトの特別セクションで説明されています。主な欠点は次の 3 つです。
  1. パフォーマンスは悪くなります。リフレクションを使用して呼び出されたメソッドは、通常の方法で呼び出されたメソッドよりもパフォーマンスが悪くなります。

  2. セキュリティ制限があります。リフレクション メカニズムを使用すると、実行時にプログラムの動作を変更できます。しかし、実際のプロジェクトに取り組んでいる職場では、これが許されない制限に直面するかもしれません。

  3. 内部情報が漏洩するリスク。リフレクションはカプセル化の原則への直接の違反であることを理解することが重要です。リフレクションにより、プライベート フィールドやメソッドなどにアクセスできるようになります。OOP の原則への直接的で重大な違反があれば、訴えられるべきであることについて言及する必要はないと思います。最も極端な場合、つまり自分では制御できない理由で問題を解決する他に方法がない場合にのみ適用されます。

反省は避けられない状況でのみ賢明に使用し、その欠点を忘れないでください。これで私たちのレッスンは終了です。かなり長くなりましたが、今日はたくさんのことを学びました:)
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION