本ページはプロモーションが含まれています

Java入門

Javaリフレクションとは?禁止場面も解説

CATEGORY
REV.
DRAWN BY
TOM
SHEETS
7枚
Reflection(使いどころと禁止場面)

トム

SESで約8年、環境を変えられないもどかしさの中にいました。32歳で自社開発企業に転職し、今はClaude Codeと自動化で、仕事も時間も組み直している途中です。
・Javaバックエンドエンジニア(Java歴12年以上)
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎
詳細なプロフィール

𝕏

Javaリフレクションは、実行時にクラスやメソッド、フィールドの情報を取得・操作できる強力な機能です。

SpringのDI(依存性注入)やJUnitのテスト実行など、普段使っているフレームワークの裏側で動いている技術でもあります。

ただし、その強力さゆえに「業務コードでは使うな」と言われることも多い、いわばJavaの"黒魔術"です。

この記事では、リフレクションの基本概念から、Class.forName・invoke・setAccessibleを使った3ステップの実装方法、そしてなぜ禁止されるのかまでを、サンプルコード付きでわかりやすく解説します。

リフレクションとは?その基本概念をわかりやすく解説

リフレクションの定義と役割

Javaリフレクションは、実行時にプログラム自身の構造(クラス、メソッド、フィールドなど)を調べたり、操作したりする機能です。

通常、Javaプログラムはコンパイル時にクラスやメソッドの呼び出し関係が決定されます。

しかし、リフレクションを使うと、プログラムの実行中、つまり動的に、以下のような操作が可能になります。

  • クラス名(文字列)から、そのクラスのClassオブジェクトを取得する
  • Classオブジェクトから、そのクラスが持つメソッドやフィールドの一覧を取得する
  • 取得したメソッドを、名前を指定して動的に呼び出す
  • 取得したフィールドの値を、直接読み取ったり書き換えたりする

「リフレクション (Reflection)」とは「反射」や「内省」を意味します。

まるでプログラムが鏡を見て、自分自身の姿形(構造)を認識し、操作するようなイメージから名付けられました。

なぜリフレクションが必要なのか

リフレクションが必要とされる主な理由は、コンパイル時ではなく実行時に、柔軟な処理を決定したいという要求があるからです。

例えば、以下のような場面ではリフレクションが必要になります。

  1. 汎用的なフレームワークの開発:SpringのようなDIコンテナは、どのクラスのどのフィールドに依存性を注入すべきか、コンパイル時には分かりません。実行時に設定ファイルやアノテーションを読み取り、リフレクションを使って対象のフィールドに値をセットします。
  2. テストツールの開発:JUnitのようなテストフレームワークは、どのメソッドがテスト対象かを知る必要があります。リフレクションを使い、@Testアノテーションが付いたメソッドを探し出して自動的に実行します。
  3. シリアライズ/デシリアライズ:Jacksonのようなライブラリは、JSONデータとJavaオブジェクトを相互に変換します。リフレクションを使い、JSONのキーとJavaクラスのフィールド名を照合し、値を動的にマッピングします。

DI・テスト実行・JSONマッピングのいずれも、プログラムを実行するまで対象のクラスやメソッドが特定できないケースです。

Javaリフレクションは、こうした動的なプログラムの実現を支える基盤技術なのです。

他の機能(例:ジェネリクスや継承)との違い

Javaにはジェネリクスや継承といった、柔軟なプログラミングを助ける機能がほかにもあります。これらとリフレクションは、目的が異なります。

  • ジェネリクスや継承: これらは主にコンパイル時の型安全性を高めたり、コードの再利用性を高めたりするための仕組みです。コンパイラが型をチェックし、安全性を保証します。
  • リフレクション: これは実行時に型の制約を超えて動的な操作を可能にする仕組みです。コンパイル時の型チェックを意図的に迂回するため、型安全性が低下する側面があります。

ジェネリクスが「コンパイル時に型をしっかり固定する」ための技術だとすれば、リフレクションは「実行時に型情報を調べて動かす」ための技術であり、アプローチが逆です。

リフレクションの3ステップ
1
Classを取得Class.forName("名前") など
2
メンバーを取得Field / Method / Constructor
3
動的に操作invoke() や set() で実行
NOTEクラス名が実行時にしかわからなくても、クラスの中身を調べて呼び出せる。ただしコンパイル時のチェックは効かない。

リフレクションの使い方3ステップ

Javaリフレクションは、以下の3ステップで使います。

  1. Classオブジェクトの取得
  2. MethodやFieldオブジェクトの取得
  3. 取得したオブジェクトの操作(メソッド呼び出し、フィールドの書き換え)

それぞれのステップを詳しく見ていきましょう。

Classクラスの取得方法

Javaリフレクションのすべての操作は、対象クラスの設計図情報を保持するClassオブジェクトから始まります。

このClassオブジェクトを取得する方法は、主に以下の3つです。

  1. Class.forName() メソッド(文字列から取得)クラスの完全修飾名(パッケージ名を含むクラス名)が文字列として分かっている場合に使います。設定ファイルからクラス名を読み込んでインスタンス化する際など、実行時にクラスを決定する場合に多用されます。
// 例: java.lang.String クラスの Class オブジェクトを取得
Class<?> clazz = Class.forName("java.lang.String");
  1. getClass() メソッド(インスタンスから取得)すでに対象クラスのインスタンスが存在する場合に使います。
String str = "Hello"; Class<?> clazz = str.getClass();
  1. .class (クラスリテラルから取得)コンパイル時にクラス名が確定している場合に使います。最も安全で高速な方法です。
Class<?> clazz = String.class;

フィールド・メソッド・コンストラクタの取得

Classオブジェクトが取得できたら、次はそのクラスの構成要素(フィールド、メソッド、コンストラクタ)を取得します。

  • フィールドの取得:java.lang.reflect.Field
    • getField(String name): publicなフィールドを1つ取得します。
    • getFields(): publicなフィールドをすべて取得します。
    • getDeclaredField(String name): privateを含む、そのクラスで宣言されたフィールドを1つ取得します。
    • getDeclaredFields(): privateを含む、そのクラスで宣言されたフィールドをすべて取得します。
  • メソッドの取得:java.lang.reflect.Method
    • getMethod(String name, Class<?>... parameterTypes): publicなメソッドを1つ取得します。
    • getMethods(): publicなメソッドをすべて取得します。
    • getDeclaredMethod(String name, Class<?>... parameterTypes): privateを含む、そのクラスで宣言されたメソッドを1つ取得します。
    • getDeclaredMethods(): privateを含む、そのクラスで宣言されたメソッドをすべて取得します。
  • コンストラクタの取得:java.lang.reflect.Constructor
    • getConstructor(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: 99

SampleUserクラスのnameとageはprivateで宣言されているため、通常は外部から変更できません。

しかし、リフレクションでgetDeclaredField()を使い、setAccessible(true)を実行することで、アクセス制限を解除し、set()で値を強制的に書き換えています。

privateフィールドが書き換わるまで
1
clazz = user.getClass();インスタンスからClassオブジェクトを取得
2
clazz.getDeclaredField("name");privateを含むnameフィールドを取得
3
nameField.setAccessible(true);アクセス制限を解除するprivate → 解除済み
4
nameField.set(user, "Taro Yamada");値を強制的に書き換えageも同様に99へ
BEFORE / 書き換え前Name: Default User, Age: 30name・ageともにprivate
→
AFTER / 書き換え後Name: Taro Yamada, Age: 99外から変えられないはずの値が変わった
NOTE上のサンプルコード(SampleUser)と同じ値です。

なお、このサンプルのように自分で作ったクラス(クラスパス上のクラス)のprivateフィールドには、Java 17以降でも従来どおりアクセスできます。

一方、StringやHashMapなどJDK内部クラスのprivateメンバーにsetAccessible(true)を呼ぶと、Java 17(JEP 403)以降はInaccessibleObjectExceptionで失敗します。以前は--illegal-accessオプションで制限を緩められましたが、Java 17からは指定しても警告が出るだけで効果はありません。

このサンプルはモジュール化されていないクラスパス上のコードで動作します。

メソッドを動的に呼び出す例

最後に、メソッド名を文字列で指定して、動的に呼び出す例です。

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)のチェック
  • 引数や戻り値の基本型(intなど)をラッパー型(Integerなど)に変換する処理

JMHなどのマイクロベンチマークでは、リフレクションによるメソッド呼び出しは通常の呼び出しより数倍〜数十倍遅くなることがあります(差は測定条件やJDKバージョンで大きく変わります)。

なお、JDK 18(JEP 416)でリフレクションの内部実装はメソッドハンドルベースに作り直されました。static finalに保持したMethodやFieldは速くなった一方、毎回取得し直すような使い方では依然として不利です。

JITコンパイラの最適化により差は縮まる傾向ですが、ループ内での繰り返し使用は依然としてボトルネックになります。

特に、ループ処理の中でリフレクションを繰り返し呼び出すようなコードは、アプリケーション全体のパフォーマンスを深刻に悪化させる原因となります。

実務では、リフレクションをキャッシュせずにループ内で毎回getDeclaredField()を呼び出しているコードに遭遇したことがあります。

Fieldオブジェクトを変数に保持するだけで処理時間が10分の1以下になったケースもあり、リフレクションを繰り返し呼び出す場合は取得結果をキャッシュするのが基本です。

カプセル化を破る危険性

リフレクションは、オブジェクト指向の重要な原則である「カプセル化」を破壊します。

カプセル化とは、クラスの内部状態(フィールド)をprivateにして隠蔽し、publicなメソッドを通じてのみ操作を許可する設計手法です。

これにより、クラスの内部実装に依存しない安全なプログラミングが可能になります。

しかし、サンプルコードで見たように、リフレクションのsetAccessible(true)を使えば、privateフィールドであっても外部から自由に読み書きできてしまいます。

これは、クラスの設計者が意図しない不正な状態変更を許すことになり、予期せぬバグやセキュリティ上の脆弱性を生み出す温床となります。

実際のコードレビューでも、setAccessible(true)が含まれるプルリクエストは「なぜリフレクションが必要なのか」の説明を求められるのが一般的です。

セキュリティリスクとJava 9以降のモジュール制限

リフレクションには、セキュリティ上のリスクもあります。

setAccessible(true)によるprivateフィールドへのアクセスは、外部から不正にデータを書き換える攻撃の足がかりになり得ます。

このリスクに対処するため、Java 9で導入されたモジュールシステム(JPMS)により、リフレクションによるアクセスは段階的に制限されてきました。

  • Java 9〜15: JDK内部への不正なリフレクションアクセスは警告付きで許可
  • Java 16(JEP 396): デフォルトで拒否に変更
  • Java 17(JEP 403): JDK内部の強いカプセル化が完了し、--illegal-accessも無効化
  • JDK 26(JEP 500): リフレクションによるfinalフィールドの書き換えに警告を出すように変更

opensされていないパッケージ(JDK内部APIや、opensを宣言していない名前付きモジュール)のprivateメンバーなど、公開されていない要素に対してsetAccessible(true)を呼ぶと、InaccessibleObjectExceptionが発生します。

リフレクションを許可するには、module-info.javaでopensディレクティブを明示的に宣言する必要があります。

これは「リフレクションで触っていい範囲を開発者が明示的に選ぶ」仕組みです。

どうしてもJDK内部やライブラリの非公開パッケージに触れる必要がある場合は、起動時に--add-opens java.base/java.lang=ALL-UNNAMEDのように開くパッケージを個別に指定します。

さらにJDK 26では、JEP 500「Prepare to Make Final Mean Final」により、リフレクションでfinalフィールドを書き換えると警告が出るようになりました。現時点では警告のみで処理は成功しますが、将来のJDKでは既定で例外(IllegalAccessException)になる予定です。

意図的に書き換えを許可する場合は、--enable-final-field-mutation=ALL-UNNAMEDのように起動オプションで明示します。「finalだけど裏から書き換える」コードは、今のうちに見直しておくのが安全です。

リフレクションが使われている代表的なフレームワーク

私自身、Spring FrameworkのDIがなぜprivateフィールドにも値をセットできるのか、Javaを学び始めた当初は不思議でした。

その答えがリフレクションだと知ったとき、フレームワークの裏側が一気に理解できた経験があります。

ここでは、代表的なフレームワークでの使われ方を見てみましょう。

SpringのDI(依存性注入)との関係

Java開発で広く使われるSpring Frameworkの中核機能であるDI (Dependency Injection) は、リフレクションの塊です。

開発者が@Autowiredアノテーションをフィールドに付けると、Springコンテナは起動時にリフレクションを使い、以下の処理を行います。

  1. @Autowiredが付いたフィールドを探す (getDeclaredFields())
  2. たとえそのフィールドがprivateであっても、setAccessible(true)でアクセス制限を解除する
  3. コンテナが管理する適切なBean(インスタンス)を、Field.set()でそのフィールドに注入(セット)する

リフレクションがあるからこそ、開発者は面倒なインスタンス管理から解放され、newを書かずに済みます。

なお、Spring Framework 6(Spring Boot 3)以降ではJakarta EEへの移行に伴い、パッケージ名がjavax.*からjakarta.*に変更されていますが、リフレクションを使ったDIの仕組み自体は同じです。

JUnitでの動的テスト実行

テストフレームワークのJUnit(2025年9月にJUnit 6.0がリリースされ、JUnit 5から続くJupiter APIが現在の主流)も、リフレクションに依存しています。

JUnitがテストを実行する際、内部では以下の処理が行われています。

  1. リフレクションを使い、テストクラス内のメソッドをすべて調べる (getDeclaredMethods())
  2. @Testアノテーションが付いているメソッドを探し出す
  3. 見つけた@Testメソッドを、リフレクションのMethod.invoke()を使って順番に実行する

この仕組みにより、私たちはメソッド名をtestXXXのように規約で縛る必要がなく、自由にメソッドを定義して@Testを付けるだけでテストが実行されます。

Jacksonなどのシリアライズライブラリでの利用

JSONとJavaオブジェクトを相互に変換するJackson(2026年現在は2.x系と3.x系が共存)やGsonといったライブラリも、リフレクションの代表的な利用者です。

  • デシリアライズ(JSON → Java):JSON文字列を受け取ると、リフレクションで変換先Javaクラスのコンストラクタ・setter・フィールドを調べます。JSONのキー名と照合して値をセットし、オブジェクトを復元します。Gsonは既定でprivateフィールドにsetAccessible(true)とField.set()で値を入れます。Jacksonは既定ではsetterを使い(Method.invoke())、フィールドへ直接入れるのは@JsonPropertyなどで対象にした場合です。
  • シリアライズ(Java → JSON):Javaオブジェクトを受け取ると、リフレクションでgetterやフィールドを調べ、Method.invoke()やField.get()で値を読み取り、JSON形式に変換します。Jacksonは既定でgetterを使い、Gsonはフィールドを直接読み取ります。

リフレクションの使いどころと禁止場面

Javaリフレクションは、実行時にプログラム自身を操作する強力な機能です。

SpringやJUnitをはじめ、現代のJava開発に欠かせないフレームワークを支えています。

一方で、Java 17の強いカプセル化やJDK 26のfinalフィールド書き換え警告、GraalVMネイティブイメージでの事前登録要件など、リフレクションを取り巻く環境は変化しています。

しかし、その力には「パフォーマンスの低下」と「カプセル化の破壊」という大きな代償が伴います。

リフレクションを使うべきケース

リフレクションの使用が許容されるのは、主に以下のようなケースです。

  • フレームワークやライブラリの開発: 設定ファイルやアノテーションに基づき、動的にクラスを操作する必要がある場合。
  • 汎用的なツールの開発: IDEやデバッガなど、あらゆるJavaコードを対象に動作する必要がある場合。

業務コードで禁止すべきケース

一方で、以下のようなケースではリフレクションの使用を避けるべきです。

  • 通常のアプリケーション開発: publicなメソッド呼び出しやインターフェースで解決できる処理。
  • パフォーマンスが要求される処理: ループ内での呼び出しなど、速度が求められる箇所。
  • 安易なprivateへのアクセス: 設計の問題をリフレクションでごまかすための使用。

リフレクションを使ってよいかの判断を流れにすると、次のようになります。

リフレクションを使ってよいかの判断(Q1から順に答える)
QUESTION / 質問
はい
いいえ
Q1. publicメソッドやインターフェースで解決できる?
使わない通常の呼び出しで書く
Q2へ
Q2. フレームワーク・ライブラリ・テストツールの開発?
Q3へ
設計を見直す業務コードでは原則禁止
Q3. ループ内など性能が重要な箇所?
Method/FieldをキャッシュまたはMethodHandleを検討したうえでQ4へ
Q4へ
Q4. JDK内部やfinalフィールドに触る?
Java 17以降は失敗・JDK 26は警告--add-opens等の明示が必要
使ってよい理由をコメントに残す

Javaリフレクションに関するよくある質問

Q1. Javaリフレクションはなぜ禁止されることが多いのですか?

A1. 主な理由は「パフォーマンスの低下」と「カプセル化の破壊」です。リフレクション経由のメソッド呼び出しは通常より低速で、privateフィールドへの不正アクセスを許すため設計原則を壊しやすく、業務コードでは避けるべきとされています。

Q2. SpringやJUnitはなぜリフレクションを使っているのですか?

A2. フレームワークはアノテーションや設定ファイルに基づいて実行時に動的にクラスを操作する必要があるため、リフレクションが必要です。フレームワーク開発者が内部で使う分には妥当ですが、利用者側の業務コードで多用するのは別問題です。

Q3. リフレクションはJava 9以降も使えますか?

A3. 使えます。自作クラスへのリフレクションは従来どおり可能ですが、モジュールシステム(JPMS)により制限が段階的に強化されています。Java 17以降はJDK内部クラスのprivateメンバーへのアクセスが既定で禁止され、名前付きモジュールではopens宣言が必要です。JDK 26ではfinalフィールドの書き換えに警告が出るようになりました。GraalVMネイティブイメージでは、さらに事前登録(GraalVM for JDK 23以降はreachability-metadata.json)が必須です。

REVISIONS / 改訂履歴
REV.DATE内容
62026-10-16図解を描き直し(図面のデザインにそろえた)
52026-10-14雰囲気だけの挿絵を外した
42026-10-04記載内容の事実確認と修正
32026-09-052026年9月時点の情報で全体を見直し
… ほか1回の改訂
12025-11-08初版
NEXT READ本棚から1本

AIで開発を組み直すなら、まずこの5冊から

入門・実践・設計まで、Claude Codeの本をレベル別に5冊選んで比べました。

→ おすすめ5冊を見る

  • この記事を書いた人
  • 最新記事

トム

SESで約8年、環境を変えられないもどかしさの中にいました。32歳で自社開発企業に転職し、今はClaude Codeと自動化で、仕事も時間も組み直している途中です。
・Javaバックエンドエンジニア(Java歴12年以上)
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎
詳細なプロフィール

𝕏

Java入門