Javaリフレクションは、実行時にクラスやメソッド、フィールドの情報を取得・操作できる強力な機能です。
SpringのDI(依存性注入)やJUnitのテスト実行など、普段使っているフレームワークの裏側で動いている技術でもあります。
ただし、その強力さゆえに「業務コードでは使うな」と言われることも多い、いわばJavaの"黒魔術"です。
この記事では、リフレクションの基本概念から、Class.forName・invoke・setAccessibleを使った3ステップの実装方法、そしてなぜ禁止されるのかまでを、サンプルコード付きでわかりやすく解説します。
リフレクションとは?その基本概念をわかりやすく解説

リフレクションの定義と役割
Javaリフレクションは、実行時にプログラム自身の構造(クラス、メソッド、フィールドなど)を調べたり、操作したりする機能です。
通常、Javaプログラムはコンパイル時にクラスやメソッドの呼び出し関係が決定されます。
しかし、リフレクションを使うと、プログラムの実行中、つまり動的に、以下のような操作が可能になります。
- クラス名(文字列)から、そのクラスの
Classオブジェクトを取得する Classオブジェクトから、そのクラスが持つメソッドやフィールドの一覧を取得する- 取得したメソッドを、名前を指定して動的に呼び出す
- 取得したフィールドの値を、直接読み取ったり書き換えたりする
「リフレクション (Reflection)」とは「反射」や「内省」を意味します。
まるでプログラムが鏡を見て、自分自身の姿形(構造)を認識し、操作するようなイメージから名付けられました。
なぜリフレクションが必要なのか
リフレクションが必要とされる主な理由は、コンパイル時ではなく実行時に、柔軟な処理を決定したいという要求があるからです。
例えば、以下のような場面でリフレクションは不可欠です。
- 汎用的なフレームワークの開発:SpringのようなDIコンテナは、どのクラスのどのフィールドに依存性を注入すべきか、コンパイル時には分かりません。実行時に設定ファイルやアノテーションを読み取り、リフレクションを使って対象のフィールドに値をセットします。
- テストツールの開発:JUnitのようなテストフレームワークは、どのメソッドがテスト対象かを知る必要があります。リフレクションを使い、@Testアノテーションが付いたメソッドを探し出して自動的に実行します。
- シリアライズ/デシリアライズ:Jacksonのようなライブラリは、JSONデータとJavaオブジェクトを相互に変換します。リフレクションを使い、JSONのキーとJavaクラスのフィールド名を照合し、値を動的にマッピングします。
DI・テスト実行・JSONマッピングのいずれも、プログラムを実行するまで対象のクラスやメソッドが特定できないケースです。
Javaリフレクションは、こうした動的なプログラムの実現を支える基盤技術なのです。
他の機能(例:ジェネリクスや継承)との違い
Javaにはジェネリクスや継承といった、柔軟なプログラミングを助ける機能がほかにもあります。これらとリフレクションは、目的が異なります。
- ジェネリクスや継承: これらは主にコンパイル時の型安全性を高めたり、コードの再利用性を高めたりするための仕組みです。コンパイラが型をチェックし、安全性を保証します。
- リフレクション: これは実行時に型の制約を超えて動的な操作を可能にする仕組みです。コンパイル時の型チェックを意図的に迂回するため、型安全性が低下する側面があります。
ジェネリクスが「コンパイル時に型をしっかり固定する」ための技術だとすれば、リフレクションは「実行時に型情報を調べて動かす」ための技術であり、アプローチが逆だと言えます。
リフレクションの使い方3ステップ

Javaリフレクションは、以下の3ステップで使います。
Classオブジェクトの取得MethodやFieldオブジェクトの取得- 取得したオブジェクトの操作(メソッド呼び出し、フィールドの書き換え)
それぞれのステップを詳しく見ていきましょう。
Classクラスの取得方法(Class.forName()・getClass()など)
Javaリフレクションのすべての操作は、対象クラスの設計図情報を保持するClassオブジェクトから始まります。
このClassオブジェクトを取得する方法は、主に以下の3つです。
- Class.forName() メソッド(文字列から取得)クラスの完全修飾名(パッケージ名を含むクラス名)が文字列として分かっている場合に使います。設定ファイルからクラス名を読み込んでインスタンス化する際など、実行時にクラスを決定する場合に多用されます。
// 例: java.lang.String クラスの Class オブジェクトを取得
Class<?> clazz = Class.forName("java.lang.String"); - getClass() メソッド(インスタンスから取得)すでに対象クラスのインスタンスが存在する場合に使います。
String str = "Hello"; Class<?> clazz = str.getClass();- .class (クラスリテラルから取得)コンパイル時にクラス名が確定している場合に使います。最も安全で高速な方法です。
Class<?> clazz = String.class;フィールド・メソッド・コンストラクタの取得
Classオブジェクトが取得できたら、次はそのクラスの構成要素(フィールド、メソッド、コンストラクタ)を取得します。
- フィールドの取得:
java.lang.reflect.FieldgetField(String name):publicなフィールドを1つ取得します。getFields():publicなフィールドをすべて取得します。getDeclaredField(String name):privateを含む、そのクラスで宣言されたフィールドを1つ取得します。getDeclaredFields():privateを含む、そのクラスで宣言されたフィールドをすべて取得します。
- メソッドの取得:
java.lang.reflect.MethodgetMethod(String name, Class<?>... parameterTypes):publicなメソッドを1つ取得します。getMethods():publicなメソッドをすべて取得します。getDeclaredMethod(String name, Class<?>... parameterTypes):privateを含む、そのクラスで宣言されたメソッドを1つ取得します。getDeclaredMethods():privateを含む、そのクラスで宣言されたメソッドをすべて取得します。
- コンストラクタの取得:
java.lang.reflect.ConstructorgetConstructor(Class<?>... parameterTypes):publicなコンストラクタを1つ取得します。getConstructors():publicなコンストラクタをすべて取得します。getDeclaredConstructor(Class<?>... parameterTypes):privateを含むコンストラクタを1つ取得します。getDeclaredConstructors():privateを含むコンストラクタをすべて取得します。
get...()系はpublicなメンバーのみ、getDeclared...()系はprivateやprotectedを含む、そのクラスで直接宣言されたメンバーのみを取得する、という違いが重要です。
取得したメソッドやフィールドを動的に操作する方法
構成要素を表すオブジェクト(MethodやField)が取得できたら、いよいよそれらを操作します。
- メソッドの呼び出し:
Method.invoke()Methodオブジェクトのinvoke()メソッドを使います。第一引数にメソッドを呼び出す対象のインスタンス、第二引数以降にメソッドに渡す引数を指定します。
// (Method オブジェクトを method として取得済みとする)
// (対象のインスタンスを instance として取得済みとする)
// "hello" という引数を渡してメソッドを呼び出す
Object result = method.invoke(instance, "hello");- フィールドの操作:
Field.get()/Field.set()Fieldオブジェクトのget()で値を読み取り、set()で値を書き込みます。
// (Field オブジェクトを field として取得済みとする)
// (対象のインスタンスを instance として取得済みとする)
// フィールドの値を読み取る
Object value = field.get(instance);
// フィールドに新しい値 "world" を書き込む
field.set(instance, "world");- インスタンスの生成:
Constructor.newInstance()ConstructorオブジェクトのnewInstance()メソッドを使います。
// (Constructor オブジェクトを constructor として取得済みとする)
// "init" という引数を渡してインスタンスを生成する
Object newInstance = constructor.newInstance("init");実際に動かして理解!リフレクションのサンプルコード

Javaリフレクションの基本的な使い方を、具体的なサンプルコードで確認しましょう。
クラス情報を出力してみる
まず、java.lang.Stringクラスの情報をリフレクションで取得し、どのようなメソッドがあるかを出力してみます。
import java.lang.reflect.Method;
public class ReflectionSample1 {
public static void main(String[] args) {
try {
// 1. Class オブジェクトの取得
Class<?> clazz = Class.forName("java.lang.String");
System.out.println("クラス名: " + clazz.getName());
// 2. メソッドの取得 (public のみ)
System.out.println("--- public メソッド一覧 ---");
Method[] methods = clazz.getMethods();
// 3. 取得した情報の利用
for (Method method : methods) {
System.out.println(method.getName());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}このコードを実行すると、Stringクラスが持つlength()やsubstring()、equals()などのpublicメソッド名が一覧で出力されます。
privateフィールドにアクセスする例
次に、Javaリフレクションの強力さ(と危険さ)が分かる、privateフィールドへのアクセスを試みます。
import java.lang.reflect.Field;
// アクセス対象のクラス
class SampleUser {
private String name = "Default User";
private int age = 30;
public void printInfo() {
System.out.println("Name: " + name + ", Age: " + age);
}
}
public class ReflectionSample2 {
public static void main(String[] args) {
SampleUser user = new SampleUser();
System.out.println("--- 変更前 ---");
user.printInfo();
try {
// 1. Class オブジェクトの取得
Class<?> clazz = user.getClass();
// 2. private フィールド (name) の取得
Field nameField = clazz.getDeclaredField("name");
// 3. アクセス制限の解除 (重要)
nameField.setAccessible(true);
// 4. private フィールドの値を書き換え
nameField.set(user, "Taro Yamada");
// age も同様に書き換え
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true);
ageField.set(user, 99);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
System.out.println("--- 変更後 ---");
user.printInfo();
}
}実行結果:
--- 変更前 ---
Name: Default User, Age: 30
--- 変更後 ---
Name: Taro Yamada, Age: 99SampleUserクラスのnameとageはprivateで宣言されているため、通常は外部から変更できません。
しかし、リフレクションでgetDeclaredField()を使い、setAccessible(true)を実行することで、アクセス制限を解除し、set()で値を強制的に書き換えています。
なお、Java 17以降では--illegal-accessオプションが削除され、モジュール外からのsetAccessible(true)はデフォルトで失敗します。
このサンプルはモジュール化されていないクラスパス上のコードで動作します。
メソッドを動的に呼び出す例
最後に、メソッド名を文字列で指定して、動的に呼び出す例です。
import java.lang.reflect.Method;
public class ReflectionSample3 {
public static void main(String[] args) {
String targetText = "Hello Java Reflection";
try {
// 1. Class オブジェクトの取得
Class<?> clazz = targetText.getClass(); // String.class と同じ
// 2. 呼び出したいメソッド (substring) の取得
// 引数が int の substring(int beginIndex) を指定
Method subMethod = clazz.getMethod("substring", int.class);
// 3. メソッドの動的呼び出し
// targetText に対して、引数 6 で substring を呼び出す
Object result1 = subMethod.invoke(targetText, 6);
System.out.println("結果1: " + result1); // "Java Reflection"
// 2. 別のメソッド (length) の取得
Method lenMethod = clazz.getMethod("length");
// 3. メソッドの動的呼び出し (引数なし)
Object result2 = lenMethod.invoke(targetText);
System.out.println("結果2: " + result2); // 21
} catch (Exception e) {
e.printStackTrace();
}
}
}getMethod()でメソッド名と引数の型を指定してMethodオブジェクトを取得し、invoke()で実行対象のインスタンスと引数を渡して呼び出せます。
リフレクションが禁止される理由と3つのリスク

Javaリフレクションは非常に強力ですが、その使用には大きな代償が伴います。むやみに使うと、深刻な問題を引き起こす可能性があります。
パフォーマンスの低下について
リフレクションによる操作は、通常のコード実行に比べて著しく遅いです。
これは、実行時に以下の処理が内部で行われるためです。
- クラス名やメソッド名を検索する処理
- 引数の型チェック
- アクセス権(
publicやprivate)のチェック setAccessible(true)によるセキュリティ機構の無効化
JMHベンチマークの測定では、リフレクションによるメソッド呼び出しは通常の呼び出しに比べて約5〜50倍遅くなります(JDK 21時点)。
JITコンパイラの最適化により差は縮まる傾向ですが、ループ内での繰り返し使用は依然としてボトルネックになります。
特に、ループ処理の中でリフレクションを繰り返し呼び出すようなコードは、アプリケーション全体のパフォーマンスを深刻に悪化させる原因となります。
実務では、リフレクションをキャッシュせずにループ内で毎回getDeclaredField()を呼び出しているコードに遭遇したことがあります。
Fieldオブジェクトを変数に保持するだけで処理時間が10分の1以下になったケースもあり、リフレクションを使う場合は取得結果のキャッシュが必須と言えます。
カプセル化を破る危険性
リフレクションは、オブジェクト指向の重要な原則である「カプセル化」を破壊します。
カプセル化とは、クラスの内部状態(フィールド)をprivateにして隠蔽し、publicなメソッドを通じてのみ操作を許可する設計手法です。
これにより、クラスの内部実装に依存しない安全なプログラミングが可能になります。
しかし、サンプルコードで見たように、リフレクションのsetAccessible(true)を使えば、privateフィールドであっても外部から自由に読み書きできてしまいます。
これは、クラスの設計者が意図しない不正な状態変更を許すことになり、予期せぬバグやセキュリティ上の脆弱性を生み出す温床となります。
実際のコードレビューでも、setAccessible(true)が含まれるプルリクエストは「なぜリフレクションが必要なのか」の説明を求められるのが一般的です。
セキュリティリスクとJava 9以降のモジュール制限
リフレクションには、セキュリティ上のリスクもあります。
setAccessible(true)によるprivateフィールドへのアクセスは、外部から不正にデータを書き換える攻撃の足がかりになり得ます。
このリスクに対処するため、Java 9以降のモジュールシステム(JPMS)ではリフレクションによるアクセスがデフォルトで制限されました。
モジュール化されたコードに対してsetAccessible(true)を呼ぶと、InaccessibleObjectExceptionが発生します。
リフレクションを許可するには、module-info.javaでopensディレクティブを明示的に宣言する必要があります。
これは「リフレクションで触っていい範囲を開発者が明示的に選ぶ」仕組みです。
リフレクションが使われている代表的なフレームワーク

私自身、Spring FrameworkのDIがなぜprivateフィールドにも値をセットできるのか、Javaを学び始めた当初は不思議でした。
その答えがリフレクションだと知ったとき、フレームワークの裏側が一気に理解できた経験があります。
ここでは、代表的なフレームワークでの使われ方を見てみましょう。
SpringのDI(依存性注入)との関係
Java開発で広く使われるSpring Frameworkの中核機能であるDI (Dependency Injection) は、リフレクションの塊です。
開発者が@Autowiredアノテーションをフィールドに付けると、Springコンテナは起動時にリフレクションを使い、以下の処理を行います。
@Autowiredが付いたフィールドを探す (getDeclaredFields())- たとえそのフィールドが
privateであっても、setAccessible(true)でアクセス制限を解除する - コンテナが管理する適切なBean(インスタンス)を、
Field.set()でそのフィールドに注入(セット)する
リフレクションがあるからこそ、開発者は面倒なインスタンス管理から解放され、newを書かずに済みます。
なお、Spring Framework 6(Spring Boot 3)以降ではJakarta EEへの移行に伴い、パッケージ名がjavax.*からjakarta.*に変更されていますが、リフレクションを使ったDIの仕組み自体は同じです。
JUnitでの動的テスト実行
テストフレームワークのJUnit(現在の主流はJUnit 5 / Jupiter)も、リフレクションに依存しています。
JUnitがテストを実行する際、内部では以下の処理が行われています。
- リフレクションを使い、テストクラス内のメソッドをすべて調べる (
getDeclaredMethods()) @Testアノテーションが付いているメソッドを探し出す- 見つけた
@Testメソッドを、リフレクションのMethod.invoke()を使って順番に実行する
この仕組みにより、私たちはメソッド名をtestXXXのように規約で縛る必要がなく、自由にメソッドを定義して@Testを付けるだけでテストが実行されます。
Jacksonなどのシリアライズライブラリでの利用
JSONとJavaオブジェクトを相互に変換するJackson(2026年現在は2.x系と3.x系が共存)やGsonといったライブラリも、リフレクションの代表的な利用者です。
- デシリアライズ(JSON → Java):JSON文字列を受け取ると、リフレクションで変換先Javaクラスのフィールド一覧を取得します。JSONのキー名とフィールド名を照合し、privateフィールドであってもsetAccessible(true)とField.set()を使って値をセットし、オブジェクトを復元します。
- シリアライズ(Java → JSON):Javaオブジェクトを受け取ると、リフレクションでフィールド一覧を取得し、Field.get()で値を読み取り、JSON形式に変換します。
まとめ:Javaリフレクションの使いどころと禁止すべき場面
Javaリフレクションは、実行時にプログラム自身を操作する強力な機能です。
SpringやJUnitをはじめ、現代のJava開発に欠かせないフレームワークを支えています。
一方で、Java 9以降のモジュールシステムによる制限強化やGraalVMネイティブイメージでの事前登録要件など、リフレクションを取り巻く環境は変化しています。
しかし、その力には「パフォーマンスの低下」と「カプセル化の破壊」という大きな代償が伴います。
リフレクションを使うべきケース
リフレクションの使用が許容されるのは、主に以下のようなケースです。
- フレームワークやライブラリの開発: 設定ファイルやアノテーションに基づき、動的にクラスを操作する必要がある場合。
- 汎用的なツールの開発: IDEやデバッガなど、あらゆるJavaコードを対象に動作する必要がある場合。
業務コードで禁止すべきケース
一方で、以下のようなケースではリフレクションの使用を避けるべきです。
- 通常のアプリケーション開発:
publicなメソッド呼び出しやインターフェースで解決できる処理。 - パフォーマンスが要求される処理: ループ内での呼び出しなど、速度が求められる箇所。
- 安易な
privateへのアクセス: 設計の問題をリフレクションでごまかすための使用。
Javaリフレクションに関するよくある質問
Javaリフレクションはなぜ禁止されることが多いのですか?
主な理由は「パフォーマンスの低下」と「カプセル化の破壊」です。リフレクション経由のメソッド呼び出しは通常より低速で、privateフィールドへの不正アクセスを許すため設計原則を壊しやすく、業務コードでは避けるべきとされています。
SpringやJUnitはなぜリフレクションを使っているのですか?
フレームワークはアノテーションや設定ファイルに基づいて実行時に動的にクラスを操作する必要があるため、リフレクションが不可欠です。フレームワーク開発者が内部で使う分には妥当ですが、利用者側の業務コードで多用するのは別問題です。
リフレクションはJava 9以降も使えますか?
使えますが、モジュールシステム(JPMS)により制限が強化されています。モジュール外部からprivateフィールドにアクセスするには明示的な許可(opens宣言)が必要になりました。GraalVMネイティブイメージでは、さらに事前登録(reflect-config.json)が必須です。


