皆さんも日常生活の中で「反省」という言葉を目にしたことがあるかもしれません。通常、この言葉は自分自身を研究するプロセスを指します。プログラミングにおいても同様の意味があり、プログラムに関するデータを検査したり、実行中にプログラムの構造や動作を変更したりするためのメカニズムです。ここで重要なことは、コンパイル時ではなく実行時に実行されるということです。しかし、なぜ実行時にコードを調べるのでしょうか? すでにおわかりでしょう :/リフレクションの概念がすぐに理解できない理由は 1 つあります。それは、この瞬間まで、作業しているクラスを常に知っていたからです。たとえば、次のようなクラスを作成できます
Cat
。
package learn.javarush;
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
、プログラムが犬か猫かに応じて動物を治療する動物病院クラスも作成しました。これらのタスクはそれほど単純ではありませんが、プログラムはコンパイル時にクラスに関して必要なすべての情報を学習します。したがって、メソッド内のmain()
オブジェクトをCat
獣医クリニック クラスのメソッドに渡すと、プログラムはこれが犬ではなく猫であることをすでに認識しています。さて、別のタスクに直面していると想像してみましょう。私たちの目標は、コード アナライザーを作成することです。CodeAnalyzer
単一のメソッドを持つクラスを作成する必要がありますvoid analyzeClass(Object o)
。このメソッドは次のことを行う必要があります。
- オブジェクトが渡されたクラスを特定し、コンソールにクラス名を表示します。
- プライベートフィールドを含むこのクラスのすべてのフィールドの名前を決定し、コンソールに表示します。
- プライベートメソッドを含むこのクラスのすべてのメソッドの名前を決定し、コンソールに表示します。
public class CodeAnalyzer {
public static void analyzeClass(Object o) {
//Вывести название класса, к которому принадлежит an object o
//Вывести названия всех переменных этого класса
//Вывести названия всех методов этого класса
}
}
これで、この問題と以前に解決した残りの問題との違いが見えてきます。この場合、問題は、メソッドに正確に何が渡されるのか、ユーザーもプログラムもわからないという事実にありますanalyzeClass()
。あなたがプログラムを作成すると、他のプログラマがそれを使用し始め、標準 Java クラスや自分が作成したクラスなど、あらゆるものをこのメソッドに渡すことができます。このクラスには、任意の数の変数とメソッドを含めることができます。言い換えれば、この場合、私たち (そして私たちのプログラム) はどのクラスを扱うことになるのか全く分かりません。それでも、私たちはこの問題を解決しなければなりません。ここで、標準 Java ライブラリである Java Reflection API が役に立ちます。Reflection API は強力な言語機能です。Oracle の公式ドキュメントには、このメカニズムは、自分が何をしているのかをよく理解している経験豊富なプログラマのみが使用することをお勧めすると記載されています。なぜ突然このような警告が事前に与えられるのかはすぐに理解できるでしょう :) 以下は、Reflection API を使用して実行できることのリストです。
- オブジェクトのクラスを調べて決定します。
- クラス修飾子、フィールド、メソッド、定数、コンストラクター、スーパークラスに関する情報を取得します。
- 実装されたインターフェイスにどのメソッドが属しているかを調べます。
- プログラムを実行するまでクラス名が不明な場合は、クラスのインスタンスを作成します。
- オブジェクトフィールドの値を名前で取得および設定します。
- オブジェクトのメソッドを名前で呼び出します。
オブジェクトのクラスを確認/決定する方法
基本から始めましょう。Java のリフレクション メカニズムへのエントリ ポイントは ですClass
。はい、とても面白いように見えますが、それがリフレクションの目的です:) class を使用してClass
、まずメソッドに渡されるオブジェクトのクラスを決定します。これを試してみましょう:
import learn.javarush.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("Barsik", 6));
}
}
コンソール出力:
class learn.javarush.Cat
2つのことに注意してください。まず、クラスを意図的にCat
別のパッケージに入れましたが、クラスの完全な名前が返されることlearn.javarush;
がわかります。getClass()
次に、変数に という名前を付けましたclazz
。少し奇妙に見えます。もちろん、これは「クラス」と呼ばれる必要がありますが、「クラス」は Java 言語の予約語であり、コンパイラはそのように変数を呼び出すことを許可しません。そこから抜け出さなければなりませんでした:) まあ、悪くないスタートでした!可能性のリストには他に何があったでしょうか?
クラス修飾子、フィールド、メソッド、定数、コンストラクター、スーパークラスに関する情報を取得する方法
これはもう、もっと面白いですよ!現在のクラスには定数も親クラスもありません。完全を期すためにそれらを追加しましょう。最も単純な親クラスを作成しましょうAnimal
。
package learn.javarush;
public class Animal {
private String name;
private int age;
}
そして、クラスの Cat
継承と 1 つの定数をクラスに 追加しましょう。Animal
package learn.javarush;
public class Cat extends Animal {
private static final String ANIMAL_FAMILY = "Семейство кошачьих";
private String name;
private int age;
//...остальная часть класса
}
これで完全なセットが揃いました!リフレクションの可能性を試してみましょう:)
import learn.javarush.Cat;
import java.util.Arrays;
public class CodeAnalyzer {
public static void analyzeClass(Object o) {
Class clazz = o.getClass();
System.out.println("Name класса: " + clazz);
System.out.println("Поля класса: " + Arrays.toString(clazz.getDeclaredFields()));
System.out.println("Родительский класс: " + clazz.getSuperclass());
System.out.println("Методы класса: " + Arrays.toString(clazz.getDeclaredMethods()));
System.out.println("Конструкторы класса: " + Arrays.toString(clazz.getConstructors()));
}
public static void main(String[] args) {
analyzeClass(new Cat("Barsik", 6));
}
}
コンソールには次のように表示されます。
Name класса: class learn.javarush.Cat
Поля класса: [private static final java.lang.String learn.javarush.Cat.ANIMAL_FAMILY, private java.lang.String learn.javarush.Cat.name, private int learn.javarush.Cat.age]
Родительский класс: class learn.javarush.Animal
Методы класса: [public java.lang.String learn.javarush.Cat.getName(), public void learn.javarush.Cat.setName(java.lang.String), public void learn.javarush.Cat.sayMeow(), public void learn.javarush.Cat.setAge(int), public void learn.javarush.Cat.jump(), public int learn.javarush.Cat.getAge()]
Конструкторы класса: [public learn.javarush.Cat(java.lang.String,int)]
授業の詳しい情報をたくさんいただきました!そして公的な部分だけではなく、プライベートな部分についても。 注意してください: private
-変数もリストに表示されます。実際、この時点でクラスの「分析」は完了したと考えられます。このメソッドを使用して、analyzeClass()
可能なことはすべて学習します。しかし、リフレクションを使用する場合、これらがすべての可能性を提供するわけではありません。単なる観察にとどまらず、積極的に行動していきましょう!:)
プログラムの実行前にクラス名が不明な場合にクラスのインスタンスを作成する方法
デフォルトのコンストラクターから始めましょう。これはまだクラスにないCat
ので、追加しましょう。
public Cat() {
}
Cat
リフレクション (メソッド) を使用して オブジェクトを作成するコードは次のようになりますcreateCat()
。
import learn.javarush.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.javarush.Cat
コンソール出力:
Cat{name='null', age=0}
これはエラーではありません。クラスメソッドで出力をプログラムしたため、値name
と値age
がコンソールに表示されます。ここでは、コンソールからオブジェクトを作成するクラスの名前を読み取ります。実行中のプログラムは、作成するオブジェクトのクラスの名前を学習します。 簡潔にするために、例自体より多くのスペースを占有しないように、適切な例外処理のコードを省略しました。もちろん、実際のプログラムでは、間違った名前が入力された場合などに対処する価値があります。デフォルトのコンストラクターは非常に単純なものなので、ご覧のとおり、それを使用してクラスのインスタンスを作成することは難しくありません :) そして、メソッドを使用して、このクラスの新しいオブジェクトを作成します。クラス コンストラクターがパラメーターを入力として受け取るかどうかは別の問題です。クラスからデフォルトのコンストラクターを削除して、コードを再度実行してみましょう。 toString()
Cat
newInstance()
Cat
null
java.lang.InstantiationException: learn.javarush.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.javarush.Cat;
import java.lang.reflect.InvocationTargetException;
public class Main {
public static Cat createCat() {
Class clazz = null;
Cat cat = null;
try {
clazz = Class.forName("learn.javarush.Cat");
Class[] catClassParams = {String.class, int.class};
cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Barsik", 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='Barsik', age=6}
プログラムで何が起こっているのかを詳しく見てみましょう。オブジェクトの配列を作成しましたClass
。
Class[] catClassParams = {String.class, int.class};
これらはコンストラクターのパラメーターに対応します (パラメーターString
とだけがありますint
)。それらをメソッドに渡しclazz.getConstructor()
、必要なコンストラクターにアクセスします。この後、必要なパラメータを指定してメソッドを呼び出しnewInstance()
、必要なクラスにオブジェクトを明示的にキャストすることを忘れないでくださいCat
。
cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Barsik", 6);
その結果、オブジェクトは正常に作成されます。コンソール出力:
Cat{name='Barsik', age=6}
次へ移りましょう :)
オブジェクトフィールドの値を名前で取得および設定する方法
別のプログラマが作成したクラスを使用していると想像してください。ただし、編集する機会はありません。たとえば、JAR にパッケージ化された既製のクラス ライブラリです。クラスコードを読み取ることはできますが、変更することはできません。このライブラリのクラス (これを古いクラスとするCat
) を作成したプログラマは、最終設計の前に十分な睡眠をとらず、フィールドのゲッターとセッターを削除しましたage
。このクラスがあなたのところにやって来ました。必要なのはプログラム内のオブジェクトだけなので、ニーズを完全に満たしますCat
。しかし、同じフィールドにはそれらが必要ですage
。これは問題です。フィールドに modifier があり、private
ゲッターとセッターがこのクラスの開発者志望者によって削除されたため、フィールドに到達できません :/ そうですね、この状況でもリフレクションが役に立ちます。Cat
私たちはクラスコードにアクセスできます。少なくとも、そのコードにどのようなフィールドがあり、それらが何と呼ばれているかを知ることができます。この情報を活用して問題を解決します。
import learn.javarush.Cat;
import java.lang.reflect.Field;
public class Main {
public static Cat createCat() {
Class clazz = null;
Cat cat = null;
try {
clazz = Class.forName("learn.javarush.Cat");
cat = (Cat) clazz.newInstance();
//с полем name нам повезло - для него в классе есть setter
cat.setName("Barsik");
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
フィールドではすべてが単純です。クラス開発者がそのセッターを提供しました。また、デフォルトのコンストラクターからオブジェクトを作成する方法もすでに知っています。 this にはメソッドがありますnewInstance()
。ただし、2 番目のフィールドをいじる必要があります。ここで何が起こっているのか見てみましょう:)
Field age = clazz.getDeclaredField("age");
ここでは、オブジェクトを使用してClass clazz
、age
を使用してフィールドにアクセスしますgetDeclaredField()
。これにより、年齢フィールドをオブジェクトとして取得できるようになりますField age
。しかし、private
フィールドには単純に値を割り当てることはできないため、これではまだ十分ではありません。これを行うには、次のメソッドを使用してフィールドを「利用可能」にする必要がありますsetAccessible()
。
age.setAccessible(true);
これが行われるフィールドには値を割り当てることができます。
age.set(cat, 6);
ご覧のとおり、一種のセッターが逆さまになっています。フィールドにField age
その値を割り当て、さらにこのフィールドが割り当てられるオブジェクトも渡します。メソッドを実行してmain()
見てみましょう。
Cat{name='Barsik', age=6}
素晴らしい、すべてやり遂げました!:) 他にどのような可能性があるか見てみましょう...
オブジェクトのメソッドを名前で呼び出す方法
前の例から状況を少し変えてみましょう。Cat
クラス開発者がフィールドを間違えたとしましょう。両方とも使用可能で、ゲッターとセッターがあり、すべて問題ありません。問題は異なります。彼は、私たちが絶対に必要とするメソッドを private にしました。
private void sayMeow() {
System.out.println("Meow!");
}
その結果、Cat
プログラム内でオブジェクトを作成しますが、そのメソッドを呼び出すことはできませんsayMeow()
。鳴かない猫が現れるでしょうか?かなり奇妙です :/ どうすればこれを修正できますか? ここでも、Reflection API が役に立ちます。必要なメソッドの名前はわかっています。残りはテクニックの問題です。
import learn.javarush.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("Barsik", 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 つです。
-
生産性が低下します。リフレクションを使用して呼び出されるメソッドは、通常に呼び出されるメソッドよりもパフォーマンスが低くなります。
-
安全上の制限があります。リフレクション メカニズムを使用すると、実行時にプログラムの動作を変更できます。ただし、実際のプロジェクトの作業環境では、これを実行できない制限がある場合があります。
-
内部情報漏洩のリスク。リフレクションの使用はカプセル化の原則に直接違反することを理解することが重要です。これにより、プライベート フィールド、メソッドなどにアクセスできるようになります。OOP 原則への直接的かつ重大な違反は、制御不能な理由で問題を解決する他の方法がない最も極端な場合にのみ使用されるべきであることは説明する必要はないと思います。
GO TO FULL VERSION