言語を選択
durumis AIが要約した文章
- リフレクションは、実行時にクラス情報にアクセスしてクラスを操作できるAPIです。
- リフレクションを使用すると、クラスのインスタンスを生成したり、フィールドやメソッドにアクセス制御子に関係なくアクセスできます。特に、フレームワークなど大規模な開発段階で依存関係を動的に管理する場合に役立ちます。
- ただし、カプセル化を損なったり、パフォーマンスを低下させる可能性があるため、必要な場合にのみ使用することをお勧めします。
リフレクションとは?
リフレクションは、ヒープ領域にロードされたClass型のオブジェクトを通じて、目的のクラスのインスタンスを作成できるようにサポートし、インスタンスのフィールドとメソッドにアクセス修飾子に関係なくアクセスできるようにサポートするAPIです。
ここで、ロードされたクラスとは、JVMのクラスローダーがクラスファイルのロードを完了した後、そのクラスの情報を含むClass型のオブジェクトを生成してメモリ上のヒープ領域に保存したものを意味します。newキーワードを使って作るオブジェクトとは違う点に注意しましょう。もし、このClass型のオブジェクトについて理解が不足している場合は、java.lang.classオブジェクトのJDKドキュメントを確認すると良いでしょう。
使用方法
リフレクションを使用する前に、ヒープ領域にロードされたクラス型のオブジェクトを取得する必要があります。合計3つの方法があります。
- クラス.classで取得
- インスタンス.getClass()で取得
- Class.forName("クラス名")で取得
public class Member {
private String name;
protected int age;
public String hobby;
public Member() {
}
public Member(String name, int age, String hobby) {
this.name = name;
this.age = age;
this.hobby = hobby;
}
public void speak(String message) {
System.out.println(message);
}
private void secret() {
System.out.println("パスワードは1234です。");
}
@Override
public String toString() {
return "Member{" +
"name='" + name + '\'' +
", age=" + age +
", hobby='" + hobby + '\'' +
'}';
}
}
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
Class memberClass = Member.class;
System.out.println(System.identityHashCode(memberClass));
Member member = new Member("ジェイオン", 23, "ダラツ開発");
Class extends Member> memberClass2 = member.getClass();
System.out.println(System.identityHashCode(memberClass2));
Class> memberClass3 = Class.forName("{パッケージ名}.Member");
System.out.println(System.identityHashCode(memberClass3));
}
}
// 実行結果
1740000325
1740000325
3つの方法で取得したClass型のインスタンスはすべて同じであることが確認できます。どの方法を使用してもハッシュ値が同じなので、状況に応じて適切に使用すれば良いでしょう。
これで、取得したClass型を通じて、そのクラスのインスタンスを作成することも、インスタンスのフィールドとメソッドにアクセス修飾子に関係なくアクセスすることもできるようになりました。まず、そのクラスのインスタンスを作成してみましょう。
public class Main {
public static void main(String[] args) throws Exception {
// Memberのすべてのコンストラクター出力
Member member = new Member();
Class extends Member> memberClass = member.getClass();
Arrays.stream(memberClass.getConstructors()).forEach(System.out::println);
// Memberのデフォルトコンストラクターによるインスタンス生成
Constructor extends Member> constructor = memberClass.getConstructor();
Member member2 = constructor.newInstance();
System.out.println("member2 = " + member2);
// Memberの別のコンストラクターによるインスタンス生成
Constructor extends Member> fullConstructor =
memberClass.getConstructor(String.class, int.class, String.class);
Member member3 = fullConstructor.newInstance("ジェイオン", 23, "ダラツ開発");
System.out.println("member3 = " + member3);
}
}
// 実行結果
public Member()
public Member(java.lang.String,int,java.lang.String)
member2 = Member{name='null', age=0, hobby='null'}
getConstructor()を使用してコンストラクターを取得し、newInstance()を使用してMemberインスタンスを動的に作成することができます。
最後に、インスタンスのフィールドとメソッドにアクセス修飾子に関係なくアクセスして使用してみましょう。
public class Main {
public static void main(String[] args) throws Exception {
Member member = new Member("ジェイオン", 23, "ダラツ開発");
Class extends Member> memberClass = member.getClass();
// フィールドアクセス
Field[] fields = memberClass.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
System.out.println(field.get(member));
}
fields[0].set(member, "ジェイオン2");
System.out.println(member);
// メソッドアクセス
Method speakMethod = memberClass.getDeclaredMethod("speak", String.class);
speakMethod.invoke(member, "リフレクションテスト");
Method secretMethod = memberClass.getDeclaredMethod("secret");
secretMethod.setAccessible(true);
secretMethod.invoke(member);
}
}
// 実行結果
ジェイオン
23
ダラツ開発
Member{name='ジェイオン2', age=23, hobby='ダラツ開発'}
リフレクションテスト
getDeclaredFileds()を使用してクラスのインスタンス変数をすべて取得し、get()を使用してフィールド値を返却し、set()を使用してフィールド値を変更できることがわかります。ここで注意すべき点は、privateアクセス修飾子を持つフィールドにアクセスする場合、setAccessible()の引数にtrueを指定する必要があるということです。
メソッドもgetDeclaredMethod()を使用して取得できます。このとき、メソッドの名前とパラメーターの型を引数として渡す必要があります。同様に、privateアクセス修飾子を持つメソッドにアクセスする場合、setAccessible()の引数にtrueを設定する必要があります。最後に、invoke()メソッドを使用して、リフレクションAPIで取得したメソッドを呼び出すことができます。
長所と短所
- 長所
- 実行時にクラスのインスタンスを作成し、アクセス修飾子に関係なくフィールドとメソッドにアクセスして、必要なタスクを実行できる柔軟性があります。
- 短所
- カプセル化を損なう可能性があります。
- 実行時にインスタンスを作成するため、コンパイル時にその型をチェックできません。
- 実行時にインスタンスを作成するため、具体的な動作フローを把握するのが難しい場合があります。
- 単純にフィールドとメソッドにアクセスする場合よりも、リフレクションを使用してアクセスする場合の方がパフォーマンスが低下する可能性があります。(すべての状況でパフォーマンスが低下するわけではありません)。
使用理由
リフレクションAPIを使用すると、実行時にクラス情報にアクセスして、クラスを自由に操作することができます。privateアクセス修飾子で宣言されたフィールドやメソッドでさえ操作可能です。オブジェクト指向設計では、カプセル化が重要であるため、使用すべきではない技術のように見えるかもしれません。
規模の小さいコンソールレベルでは、開発者はコンパイル時にプログラムで使用されるオブジェクトと依存関係をすべて把握することができます。しかし、フレームワークなど大規模な開発段階では、膨大な数のオブジェクトと依存関係を把握するのは困難です。このような場合、リフレクションを使用すると、動的にクラスを作成して依存関係を結び付けることができます。
例えば、SpringのBean Factoryを見てみると、@Controller、@Service、@Repositoryなどのアノテーションを付けるだけで、Bean Factoryが自動的にそのアノテーションが付けられたクラスを作成して管理していることがわかります。開発者は、Bean Factoryにそのクラスを知らせていませんが、それが可能な理由は、リフレクションのおかげです。実行時に、そのアノテーションが付けられたクラスを探索して発見したら、リフレクションを使用してそのクラスのインスタンスを作成し、必要なフィールドを注入してBean Factoryに保存するような形で使用されます。
もちろん、前述したように、カプセル化を損なう可能性があるため、本当に必要な場合にのみ使用するようにしましょう。