Javaリフレクションメカニズム

公開日: 2026-07-29 10:55 更新日: 2026-07-29 10:55 1872文字 10 min read ... ページビュー

本稿では,Javaリフレクションのコアコンセプトと応用について紹介する。リフレクションは、プログラムが動的にクラス情報を取得し、オブジェクトを作成し、フィールドにアクセスし、メソッドを呼び出すことができ、フレームワーク、依存注入、汎用DAOなどのシナリオで広く使用されている。この記事では、クラスオブジェクト、フィールド、メソッド、コンストラクターによる動的操作の実装方法を例で示し、汎用DAOにおけるリフレクションの実装アイデアと限界を指摘し、リフレクションを使用する際にはセキュリティ、可読性、型検証に注意を払い、ユーザー入力をクラス名やフィールド名として直接使用しないことを強調します。

Javaリフレクションメカニズム

反射の概要

Javaリフレクションとは、プログラムが実行中にクラスの構造情報を取得し、動的にオブジェクトを作成したり、フィールドにアクセスしたり、メソッドを呼び出したり、コンストラクタを呼び出したりするメカニズムです。

多くの場合、コードはコンパイル時に使用する型を指定します。リフレクションを使用すると、プログラムはクラス名や設定ファイルに基づいて実行時にどのクラスをロードするかを決定できるため、リフレクションはフレームワーク、依存注入、オブジェクトマッピング、汎用ツールクラスでよく使われます。

クラスのロードとClassオブジェクト

JVMがクラスをロードすると、その型に対して一意のClassオブジェクトが作成されます。Classオブジェクトは,クラス名,フィールド,メソッド,コンストラクタ,アノテーションなどの構造情報を保持し,リフレクションを用いるエントリである.

Classオブジェクトを取得する

Class.forName()から入手可能

try {
    Class<?> personClass = Class.forName("com.hyxy.Person");
    System.out.println(personClass.getName());
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

完全なクラス名でクラスをロードするこの方法は、クラス名が設定ファイルから取得された場合や、コンパイル時にターゲット型を直接参照できない場合に適しています。

クラスリテラルで取得

Class<Person> personClass = Person.class;

コンパイル時にターゲット型がわかっている場合は、类型.classを最も簡単に使用し、ClassNotFoundExceptionをスローしません。

オブジェクトからのアクセス

Person person = new Person();
Class<?> personClass = person.getClass();

オブジェクトがすでに保持されているが、そのランタイム型を取得する必要がある場合、getClass()を呼び出すことができます。

public static void printType(Object object) {
    Class<?> objectClass = object.getClass();
    System.out.println(objectClass.getName());
}

Classクラスの一般的な方法

方法作用
getNameパッケージ名を含む完全なクラス名の取得
getSimpleNameパッケージ名を含まないクラス名の取得
getDeclaredField String name指定された名前の現在のクラス内のフィールドを取得する
getDeced現在のクラス宣言の全フィールドを取得
getDecedString name Class<>... parerTypesメソッド名とパラメータの型に基づいてメソッドを取得
getDeclaredMethods現在のクラス宣言のすべてのメソッドを取得
getDecedConstrClass <>... parameterTypesパラメータの型に基づいてコンストラクタを取得する
getDecedConstrors現在のクラス宣言のすべてのコンストラクタを取得

Class.newInstance()は古い。オブジェクトを作成するときは、コンストラクタのnewInstance()を呼び出す前にコンストラクタを取得してください。

Object object = personClass.getDeclaredConstructor().newInstance();

Fieldオペレーションフィールドの使用

Fieldオブジェクトは、クラス内で宣言されたフィールドを表します。Fieldでは、フィールド名、フィールドタイプ、指定されたオブジェクトのフィールドの読み取りや割り当てを取得できます。

一般的な方法は以下の通り

方法作用
getNameフィールド名の取得
getTypeフィールドの種類を取得
setAccessible true非パブリックフィールドへの反射的アクセスを許可する
set Object object Object value指定したオブジェクトのフィールドに値を割り当てる
get Object object指定されたオブジェクトのフィールド値の読み取り

次の例では、Studentオブジェクトを作成し、反射によってそのnameフィールドを修正します。

package com.hyxy;

import java.lang.reflect.Field;

public class FieldTest {
    public static void main(String[] args) {
        try {
            Class<?> studentClass = Class.forName("com.hyxy.Student");
            Object student = studentClass.getDeclaredConstructor().newInstance();

            Field nameField = studentClass.getDeclaredField("name");
            nameField.setAccessible(true);
            nameField.set(student, "Tom");

            Object name = nameField.get(student);
            System.out.println(name);
        } catch (ReflectiveOperationException e) {
            e.printStackTrace();
        }
    }
}

getDeclaredField()はプライベートフィールドを含む現在のクラス宣言のフィールドを取得できますが、自動的に親クラスを検索しません。プライベートフィールドへのアクセスには通常、setAccessible(true)を呼び出す必要がある。

Methodを使用したメソッドの呼び出し

Methodオブジェクトは、クラス内のメソッドを表します。invoke()を呼び出すと、最初の引数はメソッドが属するオブジェクトであり、それ以降の引数はメソッドを呼び出すために必要な引数です。

package com.hyxy;

import java.lang.reflect.Method;

public class MethodTest {
    public static void main(String[] args) {
        try {
            Class<?> studentClass = Class.forName("com.hyxy.Student");
            Object student = studentClass.getDeclaredConstructor().newInstance();

            Method method1 = studentClass.getDeclaredMethod("study");
            Method method2 = studentClass.getDeclaredMethod(
                    "study",
                    String.class,
                    int.class
            );

            method1.setAccessible(true);
            method2.setAccessible(true);

            method1.invoke(student);
            method2.invoke(student, "Java", 24);
        } catch (ReflectiveOperationException e) {
            e.printStackTrace();
        }
    }
}

オーバーロードされたメソッドを検索するときは、メソッド名だけでなく、引数型の正確なリストも提供します。メソッド呼び出し中にターゲットメソッド内でスローされる例外はInvocationTargetExceptionとしてラップされます。

静的メソッドの場合、invoke()を呼び出すときに最初の引数をnullに渡すことができます。

method.invoke(null, "参数");

Constructorでオブジェクトを作成するには

Constructorオブジェクトは、クラス内のコンストラクタを表します。パラメータ型から指定したコンストラクタを取得し、newInstance()を呼び出してオブジェクトを作成します。

package com.hyxy;

import java.lang.reflect.Constructor;

public class ConstructorTest {
    public static void main(String[] args) {
        try {
            Class<Student> studentClass = Student.class;
            Constructor<Student> constructor = studentClass.getDeclaredConstructor(
                    String.class,
                    int.class,
                    int.class
            );

            constructor.setAccessible(true);
            Student student = constructor.newInstance("Tom", 20, 110011);

            System.out.println(
                    student.getName() + "," +
                    student.getAge() + "," +
                    student.getNo()
            );
        } catch (ReflectiveOperationException e) {
            e.printStackTrace();
        }
    }
}

リフレクション汎用 DAOの実装

基本的な考え

従来のDAOは通常、各テーブルに追加、削除、変更、チェックコードを記述します。汎用 DAOは、リフレクションによってエンティティークラスのクラス名とフィールド情報を読み取り、SQLを動的に生成することができる。

簡単な合意は以下の通り。

  • エンティティークラス名はテーブル名に対応します。
  • エンティティークラスのフィールド名はカラム名に対応します。
  • プライマリ·キー·フィールド名は呼び出し元によって渡されます。
  • インクリメンタル·プライマリキーは、挿入時にプレースホルダパラメータとして渡されません。

実際のプロジェクトでは、クラス名とテーブル名、フィールド名とカラム名はしばしば同一ではないため、マッピングはしばしば注釈とともに行われます。

一般的な新規メソッドの例

package com.hyxy.dao;

import org.apache.commons.dbutils.QueryRunner;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class CommonDao {
    public int insert(Object entity, String primaryKeyName, boolean autoIncrement) {
        Class<?> entityClass = entity.getClass();
        String tableName = entityClass.getSimpleName();
        Field[] fields = entityClass.getDeclaredFields();

        List<String> columnNames = new ArrayList<>();
        List<String> placeholders = new ArrayList<>();
        List<Object> parameterValues = new ArrayList<>();

        for (Field field : fields) {
            String fieldName = field.getName();

            if (autoIncrement && fieldName.equals(primaryKeyName)) {
                continue;
            }

            field.setAccessible(true);
            columnNames.add(fieldName);
            placeholders.add("?");

            try {
                parameterValues.add(field.get(entity));
            } catch (IllegalAccessException e) {
                throw new RuntimeException("读取字段失败:" + fieldName, e);
            }
        }

        String sql = "insert into " + tableName +
                "(" + String.join(",", columnNames) + ") values(" +
                String.join(",", placeholders) + ")";

        QueryRunner queryRunner = new QueryRunner();

        try (Connection connection = DBUtil.getConnection()) {
            return queryRunner.update(
                    connection,
                    sql,
                    parameterValues.toArray()
            );
        } catch (SQLException e) {
            throw new RuntimeException("执行新增操作失败", e);
        }
    }
}

この例では、リフレクションとJDBCカプセル化の基本的な組み合わせを示しますが、以下の制限があります。

  • エンティティークラス名は、テーブル名と一貫している必要があります。
  • フィールド名はカラム名と一致する必要があります。
  • フィールドの順序はリフレクションに依存しており、データベースの固定順序として使用しないでください。
  • 実際のプロジェクトでは、主キー、継承フィールド、静的フィールド、トランザクション、例外システムも扱う必要があります。
  • テーブル名と列名は事前コンパイルされたプレースホルダを使用できないため、信頼できるマッピング情報から取得する必要があり、ユーザー入力は直接使用できません。

反射の使用に関する注意事項

リフレクションはプログラムのダイナミクスを向上させるが、コードの可読性を低下させ、コンパイル時の型チェックの一部をバイパスする。使用にあたっては以下の原則に従ってください。

  • 直接呼び出す場合は、通常のJavaコードを優先します。
  • リフレクションエントリのタイプとパラメータのチェックを行います。
  • 未検証のユーザー入力をクラス名、メソッド名、テーブル名、カラム名として直接使用しないでください。
  • ReflectiveOperationExceptionに対して一括処理を行った.
  • モジュラー Javaプロジェクトでは、非 publicメンバへのアクセスもモジュールアクセス規則によって制限される場合があります。

気に入ったならばコメントを残してくださいね~

... ページビュー
© 2026 跨越星轨的客 @Hoshiumi
Powered by theme astro-koharu · Inspired by Shoka