R-01: リフレクション基本
java.lang.reflect パッケージを使って、 実行時にクラスの情報を取得したり、private メンバーへアクセスしたり、メソッドを動的に呼び出す方法を学びます。
リフレクションとは
リフレクション(Reflection)とは、プログラムの実行中(ランタイム)に自分自身のクラス構造を調べたり、 変更したりする仕組みです。通常はコンパイル時に決まっているクラス名・フィールド・メソッドを、 実行時に動的に取得・操作できます。
フレームワークやライブラリの内部では広く使われています。たとえば Spring の DI(依存性注入)、 Jackson の JSON シリアライズ・デシリアライズ、JUnit のテストメソッド検出などは、 いずれもリフレクションによって実現されています。
主な用途
- フレームワーク・DI: クラス名の文字列から動的にオブジェクトを生成する (例: 設定ファイルに書いたクラス名をインスタンス化する)
- シリアライズ: オブジェクトのフィールドを列挙して JSON や XML に変換する
- テスト: private メソッドをテストするため、テストコードからアクセスする
- デバッグ・ツール: オブジェクトの状態を実行時に検査・出力する
サンプルコード
Java 8 からリフレクション API が利用できます。getDeclaredFields() / getDeclaredMethods() で private メンバーも含めて取得でき、setAccessible(true) でアクセス制限を解除できます。
よくあるミス・注意点
⚠️ setAccessible(true) はセキュリティポリシーに違反する可能性がある
setAccessible(true) を使うと private フィールドやメソッドにアクセスできますが、 Java モジュールシステム(Java 9 以降の JPMS)や SecurityManager が有効な環境ではInaccessibleObjectException やSecurityException がスローされることがあります。 本番コードでは設計を見直し、public API を使うようにしましょう。
⚠️ リフレクションは通常の呼び出しより遅い
リフレクションによるメソッド呼び出しは、通常の直接呼び出しに比べて数倍〜数十倍遅くなることがあります。 JIT コンパイラによる最適化も効きにくいため、ループ内での頻繁なリフレクション呼び出しは避けましょう。 一度取得した Method オブジェクトやField オブジェクトはキャッシュして再利用することを検討してください。
⚠️ Class.forName() はクラスローダーに依存する
Class.forName("クラス名") は 完全修飾クラス名(パッケージ名を含む名前)で指定する必要があります。 内部クラスの場合は OuterClass$InnerClass のように$ で区切ります。 クラスが見つからない場合は ClassNotFoundException がスローされます。
テストする観点
getDeclaredFields()で取得したフィールド名・型・修飾子が正しいことsetAccessible(true)の後に private フィールドの値を取得できることgetDeclaredMethod()+invoke()で private メソッドを呼び出せること- コンストラクタ経由でインスタンスを生成し、メソッドを呼び出した結果が正しいこと
- 存在しないフィールド名を指定したときに
NoSuchFieldExceptionがスローされること(境界値) - 存在しないメソッド名を指定したときに
NoSuchMethodExceptionがスローされること(境界値)