CodeGym /コース /JAVA 25 SELF /リフレクションによるオブジェクト生成

リフレクションによるオブジェクト生成

JAVA 25 SELF
レベル 62 , レッスン 3
使用可能

1. リフレクションでオブジェクトを生成する

コンパイル時に型が分からないままオブジェクトを生成したいことがあります。たとえば、クラス名が設定から渡される場合や、汎用的なフレームワーク(シリアライザ、DIコンテナ)を書いている場合です。通常のコードなら次のように書きます:

User user = new User("Ivan", 25);

しかし、クラス名が User であることも、そのコンストラクタの引数も分からないとしたら? そこで役に立つのがリフレクションです。

廃れた方法: Class.newInstance()

かつての Java には、引数なしの public コンストラクタ経由でオブジェクトを生成する Class.newInstance() というメソッドがありました:

Class<?> clazz = Class.forName("com.example.User");
Object obj = clazz.newInstance();

重要: このメソッドは Java 9 以降 deprecated で、推奨されません。コンストラクタを選べず、エラー原因を隠し、引数なしの public コンストラクタにしか対応しません。

現代的な方法: コンストラクタを使う

実際のコードでは、public で引数なしのコンストラクタがあるとは限りません。今の推奨は、getConstructor(...) または getDeclaredConstructor(...) で必要なコンストラクタを取得し、その newInstance(...) を呼び出す方法です。

例: 引数付きオブジェクトの生成

public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
import java.lang.reflect.Constructor;

Class<?> clazz = Class.forName("User");
// String, int を受け取るコンストラクタを取得
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
// オブジェクトを生成し、引数を渡す
Object user = constructor.newInstance("Ivan", 25);

System.out.println(user); // toString が定義されていれば表示される

ここで何が起きているか?

  • シグネチャで目的のコンストラクタを取得する。
  • その newInstance(...) を呼び出し、引数を渡す。
  • Object 型のオブジェクトを得る(型が分かっていれば User にキャストできる)。

コンストラクタが private の場合?

Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true); // マジック! これで private コンストラクタを呼べる。
Object user = constructor.newInstance("Ivan", 25);

注意: 乱用は禁物—カプセル化を破ります。モジュール化されたアプリ(Java 9+)ではアクセス制限も適用されます。

オブジェクト生成方法の比較表

方法 速度 型安全性 いつ使うか
new User()
最も速い 完全 可能な限り常に
Constructor.newInstance()
遅い 失われる フレームワーク、プラグイン
MethodHandle
中程度 部分的 高パフォーマンスなライブラリ
ファクトリ 速い 完全 柔軟なオブジェクト生成

2. リフレクションでメソッドを呼び出す

コンパイル時に名前が分からなくても、あるいは private でも、任意のメソッドを呼び出せます.

メソッドの取得

import java.lang.reflect.Method;

Class<?> clazz = user.getClass();
// 名前と引数型から public メソッドを取得
Method method = clazz.getMethod("getName"); // 引数なし
Object result = method.invoke(user); // メソッドを引数なしで呼び出し

System.out.println(result); // ユーザー名が出力される

引数付きメソッドの呼び出し

Method setName = clazz.getMethod("setName", String.class);
setName.invoke(user, "Petr"); // 新しい名前を設定

private メソッドの呼び出し

Method secret = clazz.getDeclaredMethod("secretMethod", int.class);
secret.setAccessible(true); // アクセス制御を外す
Object secretResult = secret.invoke(user, 123);

重要: すべての引数はオブジェクト配列(varargs)として渡されます。メソッドが何かを返す場合、結果は Object として返ってきます。

3. リフレクションでフィールドにアクセスする

シリアライズやテストなどで、private であってもフィールドの値を読み書きしたいことがあります。

フィールドの取得

import java.lang.reflect.Field;

Class<?> clazz = user.getClass();
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true); // フィールドが public でない場合

// 値の読み取り
Object age = ageField.get(user);
System.out.println("年齢: " + age);

// 値の変更
ageField.set(user, 42);
System.out.println("新しい年齢: " + ageField.get(user));

static フィールドの扱い

フィールドが static の場合、get/set の第1引数にはオブジェクトの代わりに null を渡します:

Field staticField = clazz.getDeclaredField("counter");
staticField.setAccessible(true);
staticField.set(null, 100); // static フィールドではインスタンスは不要

4. 制約と例外

リフレクションの利用には多くのチェック例外が伴います。代表的なもの:

  • ClassNotFoundException — 指定した名前のクラスが見つからない。
  • NoSuchMethodException — そのシグネチャのコンストラクタ/メソッドがない。
  • NoSuchFieldException — フィールドが見つからない。
  • IllegalAccessException — メンバーにアクセスできない(例: setAccessible(true) なしで private メソッドを呼んだ)。
  • InstantiationException — クラスが抽象/インターフェースのためインスタンス化できない。
  • InvocationTargetException — 呼び出したコンストラクタ/メソッドの内部で例外が発生。

処理例:

try {
    // ... リフレクションのコード ...
} catch (ReflectiveOperationException e) {
    e.printStackTrace();
}

細部が重要でない場合は、共通のスーパークラス ReflectiveOperationException(Java 7+)を捕捉するのが便利です.

5. 実践: ミニプログラム「動的インスタンシエータ」

実験用のクラス例

public class Person {
    private String name;
    private int age;

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

    private void sayHello() {
        System.out.println("こんにちは、私の名前は " + name + "、年齢は " + age + " 歳です。");
    }
}

動的な生成と操作

import java.lang.reflect.*;

public class ReflectionDemo {
    public static void main(String[] args) {
        try {
            // 1. 名前から Class オブジェクトを取得
            Class<?> clazz = Class.forName("Person");

            // 2. コンストラクタを取得してオブジェクトを生成
            Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
            Object person = constructor.newInstance("Alisa", 30);

            // 3. private メソッド sayHello を呼び出す
            Method sayHello = clazz.getDeclaredMethod("sayHello");
            sayHello.setAccessible(true);
            sayHello.invoke(person); // こんにちは、私の名前は Alisa、年齢は 30 歳です。

            // 4. private フィールド name を変更
            Field nameField = clazz.getDeclaredField("name");
            nameField.setAccessible(true);
            nameField.set(person, "Bob");

            // 5. 再度 sayHello を呼び出す
            sayHello.invoke(person); // こんにちは、私の名前は Bob、年齢は 30 歳です。

        } catch (ReflectiveOperationException e) {
            e.printStackTrace();
        }
    }
}

注意点:

  • コードが Person 型を直接知ることなく一連の操作ができる。
  • JVM のセキュリティポリシーやモジュール設定が許せば、private フィールドの変更や private メソッドの呼び出しも可能。

6. 実際のアプリとどう関係するか?

リフレクションは多くの人気ライブラリ/フレームワークの基盤です:

  • JUnit: @Test アノテーション付きメソッドの探索、テストインスタンスの生成、メソッド呼び出し。
  • Spring: DI、Bean の生成、自動配線。
  • JacksonGson: オブジェクトのフィールドのシリアライズ/デシリアライズ。
  • Hibernate: エンティティのフィールドアクセス、プロキシ、遅延読み込み。

7. 図解: リフレクションでオブジェクトが生成される流れ

flowchart TB
    A["クラス名 (String)"] --> B["Class.forName"]
    B --> C["Class<?>"]
    C --> D["getConstructor(...)"]
    D --> E["Constructor<?>"]
    E --> F["newInstance(...)"]
    F --> G["Object"]

8. リフレクションでよくあるミス

エラー №1: 間違ったコンストラクタ。 2 つの引数が必要なのに getConstructor(String.class) を呼ぶなど、シグネチャが誤っていると NoSuchMethodException。常にシグネチャを確認しましょう。

エラー №2: アクセス権がない。 setAccessible(true) なしで private なコンストラクタ/メソッドを呼ぶと IllegalAccessException。そして、Java 9+ のモジュールシステムが追加の制約を課す場合があることも忘れずに。

エラー №3: 型の問題。 リフレクション経由の引数や戻り値はすべて Object。誤ったキャストは ClassCastException

エラー №4: 例外を処理しない。 リフレクションは多数のチェック例外を投げます。たとえば共通の ReflectiveOperationException でまとめて処理しないと、コードはコンパイルできません。

エラー №5: カプセル化の破壊。 安易な setAccessible(true) の使用は「他人の冷蔵庫を勝手に開ける」ようなもの。真に必要な場合だけに留め、セキュリティ要件を考慮しましょう。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION