【プログラミングしてみよう44】JavaのReflection入門

Java

前回はCompletableFutureを使った非同期処理を学びました。

今回は「Reflection(リフレクション)」について解説します。 Reflectionを使うことで、クラスのフィールドやメソッドの情報をプログラムの実行時に調べたり、動的に呼び出したりすることができます。フレームワークやライブラリの内部でも広く使われている仕組みです。

Reflectionとは

通常のJavaプログラムでは、使うクラスやメソッドをコンパイル時に決めておく必要があります。Reflectionを使うと、クラス名・フィールド名・メソッド名を文字列として実行時に指定し、動的に操作できるようになります。

// 通常の書き方(コンパイル時に決まる)
Person p = new Person();
p.setName("ぱんまる");

// Reflectionを使った書き方(実行時に決まる)
Class<?> cls = Class.forName("Person");
Object p = cls.getDeclaredConstructor().newInstance();
Method m = cls.getMethod("setName", String.class);
m.invoke(p, "ぱんまる");

Reflectionは強力ですが、型安全性が低下しパフォーマンスも通常より落ちるため、通常のコードでは使わず、フレームワーク開発やテストツールなど特定の用途に限って使うのが一般的です。

サンプルコード(クラス情報の取得)

ファイル作成

「Java32」フォルダに ReflectionTest.java という名前でファイルを作成します

コード入力

以下のコードを入力します。

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.util.ArrayList;

public class ReflectionTest {
    public static void main(String[] args) {
        // クラスオブジェクトを取得する3つの方法
        Class<String> cls1 = String.class;
        Class<?> cls2 = "hello".getClass();
        try {
            Class<?> cls3 = Class.forName("java.lang.String");
            System.out.println("クラス名: " + cls3.getName());
            System.out.println("シンプル名: " + cls3.getSimpleName());
        } catch (ClassNotFoundException e) {
            System.out.println("クラスが見つかりません");
        }

        // Stringクラスのメソッド一覧を表示
        System.out.println("\n--- Stringクラスのメソッド(一部) ---");
        Method[] methods = cls1.getMethods();
        int count = 0;
        for (Method method : methods) {
            System.out.println(method.getName() + "()");
            if (++count >= 5) {
                System.out.println("... 他" + (methods.length - 5) + "個");
                break;
            }
        }

        // ArrayListクラスのフィールドとコンストラクタ
        Class<ArrayList> listCls = ArrayList.class;
        System.out.println("\n--- ArrayListのコンストラクタ ---");
        for (Constructor<?> constructor : listCls.getConstructors()) {
            System.out.println(constructor);
        }
    }
}

コンパイル

javac ReflectionTest.java

実行

java ReflectionTest

実行結果は次のようになります。

クラス名: java.lang.String
シンプル名: String

--- Stringクラスのメソッド(一部) ---
equals()
length()
toString()
hashCode()
getChars()
... 他88個

--- ArrayListのコンストラクタ ---
public java.util.ArrayList()
public java.util.ArrayList(int)
public java.util.ArrayList(java.util.Collection)

サンプルコード(フィールドとメソッドの動的操作)

ファイル作成

「Java32」フォルダに Person.javaReflectionDynamicTest.java という名前でファイルを作成します

コード入力

Person.java に以下のコードを入力します。

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

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

    public String getName() { return name; }
    public int getAge() { return age; }

    private String secret() {
        return "これは秘密のメソッドです";
    }

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

ReflectionDynamicTest.java に以下のコードを入力します。

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectionDynamicTest {
    public static void main(String[] args) throws Exception {
        Person person = new Person("ぱんまる", 25);
        System.out.println("元のオブジェクト: " + person);

        // privateフィールドへのアクセス
        Field nameField = Person.class.getDeclaredField("name");
        nameField.setAccessible(true); // privateでもアクセス可能にする
        String name = (String) nameField.get(person);
        System.out.println("nameフィールド: " + name);

        // privateフィールドの値を変更
        nameField.set(person, "たろう");
        System.out.println("name変更後: " + person);

        // publicメソッドの動的呼び出し
        Method getAgeMethod = Person.class.getMethod("getAge");
        int age = (int) getAgeMethod.invoke(person);
        System.out.println("getAge()の結果: " + age);

        // privateメソッドの呼び出し
        Method secretMethod = Person.class.getDeclaredMethod("secret");
        secretMethod.setAccessible(true);
        String secret = (String) secretMethod.invoke(person);
        System.out.println("secret()の結果: " + secret);

        // クラス名からインスタンスを動的に生成
        String className = "Person";
        Class<?> cls = Class.forName(className);
        Object newPerson = cls.getDeclaredConstructor(String.class, int.class)
                             .newInstance("はなこ", 22);
        System.out.println("\n動的生成: " + newPerson);
    }
}

コンパイル

javac Person.java ReflectionDynamicTest.java

実行

java ReflectionDynamicTest

実行結果は次のようになります。

元のオブジェクト: Person{name='ぱんまる', age=25}
nameフィールド: ぱんまる
name変更後: Person{name='たろう', age=25}
getAge()の結果: 25
secret()の結果: これは秘密のメソッドです
動的生成: Person{name='はなこ', age=22}

コードの解説

クラスオブジェクト(Class<?>)を取得するには、クラス名.classインスタンス.getClass()Class.forName("クラス名") の3つの方法があります。Class.forName() は文字列でクラスを指定できるため、実行時にクラスを動的に決める場合に使います。

getDeclaredField("name") は宣言されたフィールドをアクセス修飾子に関わらず取得します。getField()public なフィールドのみ取得します。setAccessible(true) をつけることで private なフィールドやメソッドにもアクセスできるようになります。これがReflectionの強力な点であり、慎重に使うべき点でもあります。

method.invoke(object, 引数...) でメソッドを動的に呼び出せます。以前学んだアノテーションでも getAnnotation() を使ってReflectionでアノテーション情報を取り出していましたが、あれも同じReflectionの仕組みです。

getDeclaredConstructor(String.class, int.class).newInstance("はなこ", 22) のように、コンストラクタの引数の型を指定してインスタンスを動的に生成できます。SpringなどのフレームワークはこのReflectionの仕組みを使って、設定ファイルやアノテーションに書かれたクラス名からインスタンスを自動生成しています。

まとめ

Reflectionを使うことで、クラスのフィールド・メソッド・コンストラクタをプログラム実行時に調べたり動的に操作したりできます。getDeclaredFieldgetDeclaredMethodprivate メンバにもアクセスでき、setAccessible(true) でアクセス制限を解除できます。強力な反面、型安全性の低下・パフォーマンスの劣化・カプセル化の破壊といったデメリットもあるため、通常のアプリコードでは使わずフレームワーク開発などに限るのが賢明です。SpringやJUnitなどの裏側でReflectionが動いていることを知っておくと、フレームワークの動作原理への理解が深まります。

2026/07/23(木) ぱんまる

コメント

タイトルとURLをコピーしました