banner
jzman

jzman

Coding、思考、自觉。
github

Javaシリーズのリフレクション

最近の知識は比較的断片的で、文書に整理するのには適していません。以前の反射に関する学習ノートを一篇、主な内容は以下の通りです。

  1. 反射メカニズム
  2. 反射によるクラスの情報取得
  3. 反射によるクラスの情報操作
  4. 反射によるジェネリックの取得
  5. 反射によるアノテーション情報の取得

反射メカニズム#

Java の反射メカニズムは、実行状態において任意のクラスのすべての属性とメソッドを知ることができることを指します。反射は、コードが実行される際にクラスの情報を動的に取得することができるメカニズムであり、コンパイル時には取得できないクラスの情報を反射を通じて取得できます。任意のクラスがクラスローダー(ClassLoader)によって初めてロードされると、自動的にそのクラスに対応する Class オブジェクトが生成されます。この Class オブジェクトは、対応するクラスのすべての情報を保存します。このように、任意のクラスがクラスローダーによってロードされた後に、Class オブジェクトの情報を動的に取得し、Class オブジェクトの属性やメソッドを動的に操作する機能を Java の反射メカニズムと呼びます。

Java の反射メカニズムの鍵は、特定の任意のクラスの Class オブジェクトを取得することです。以下の内容は、この Class オブジェクトを通じてクラスに関連する情報を取得し、動的に操作する方法です。後の文脈で反射を使用するために、ここで User クラスを作成します。具体的には以下の通りです:

package com.manu.reflection.bean;

/**
 * 反射テストクラス
 */
public class User {
	
	private int id;
	private String name;
	private String password;
	
	public User() {
		super();
	}
	public User(int id, String name, String password) {
		super();
		this.id = id;
		this.name = name;
		this.password = password;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	@Override
	public String toString() {
		return "User [id=" + id + ", name=" + name + ", password=" + password + "]";
	}
}

反射によるクラスの情報取得#

ここでは、クラスの基本情報、例えばクラスのコンストラクタ、属性、メソッドなどを取得する方法をまとめます。特定のクラスに対応する Class オブジェクトの getter メソッドを通じて、クラスの名前、コンストラクタ、属性、メソッドなどを取得できます。以下に、特定のクラスのコンストラクタを取得する方法の例を示します:

  • clazz.getConstructors (): 特定のクラス内の public 修飾子を持つすべてのコンストラクタを取得します。getFields ()、getMethods () には、親クラスから継承された public 修飾の属性やメソッドも含まれます。
  • clazz.getDeclaredConstructors (): 特定のクラス内のすべての宣言されたコンストラクタを取得します。本クラスに限定されます。

また、特定のコンストラクタ、属性、メソッドなどを取得することもできます。以下のコードは、主に定義された(Declared)コンストラクタ、属性、メソッドを取得する例です。参考にしてください:

/**
 * 反射によるクラスの情報取得
 */
private static void getReflectClassInfo() {
	try {
		//特定のクラスのClassオブジェクトを取得
		String name = "com.manu.reflection.bean.User";
		Class clazz = Class.forName(name);
		
		//反射によるクラスの名前取得
		System.out.println("----------反射によるクラスの名前取得----------");
		String n1 = clazz.getName(); // 完全なパス:パッケージ名+クラス名 (com.manu.reflection.bean.User)
		String n2 = clazz.getSimpleName(); // クラス名(User)
		System.out.println("クラスの名前取得n1:"+n1);
		System.out.println("クラスの名前取得n2:"+n2);
		
		//反射によるクラスのコンストラクタ取得
		System.out.println("----------反射によるクラスのコンストラクタ取得----------");
		Constructor<User> c1 = clazz.getDeclaredConstructor(null);
		System.out.println("無引数コンストラクタ取得:"+c1);
		Constructor<User> c2 = clazz.getDeclaredConstructor(int.class,String.class,String.class);
		System.out.println("有引数コンストラクタ取得:"+c2);
		Constructor[] constructors = clazz.getDeclaredConstructors();
		for(Constructor c: constructors) {
			System.out.println("すべてのコンストラクタ取得:"+c);
		}
		
		//反射によるクラスの属性取得
		System.out.println("----------反射によるクラスの属性取得----------");
		Field f1 = clazz.getDeclaredField("name");
		System.out.println("nameという名前の属性取得:"+f1);
		Field[] fields = clazz.getDeclaredFields();
		for(Field f : fields) {
			System.out.println("すべての属性取得:"+f);
		}
		
		//反射によるクラスのメソッド取得
		System.out.println("----------反射によるクラスのメソッド取得----------");
		Method m1 = clazz.getDeclaredMethod("getName", null); // 無引数メソッド取得
		Method m2 = clazz.getDeclaredMethod("setName", String.class); // 有引数メソッド取得
		System.out.println("getNameという名前のメソッドm1取得:"+m1);
		System.out.println("setNameという名前のメソッドm2取得:"+m2);
		Method[] methods = clazz.getDeclaredMethods();
		for(Method m: methods) {
			System.out.println("すべてのメソッド取得:"+m);
		}
	
	} catch (Exception e) {
		e.printStackTrace();
	}
}

反射を通じて特定のクラスの関連情報を取得する方法は上記の通りです。上記コードの実行結果は以下の通りです:

----------反射によるクラスの名前取得----------
クラスの名前取得n1:com.manu.reflection.bean.User
クラスの名前取得n2:User
----------反射によるクラスのコンストラクタ取得----------
無引数コンストラクタ取得:public com.manu.reflection.bean.User()
有引数コンストラクタ取得:public com.manu.reflection.bean.User(int,java.lang.String,java.lang.String)
すべてのコンストラクタ取得:public com.manu.reflection.bean.User()
すべてのコンストラクタ取得:public com.manu.reflection.bean.User(int,java.lang.String,java.lang.String)
----------反射によるクラスの属性取得----------
nameという名前の属性取得:private java.lang.String com.manu.reflection.bean.User.name
すべての属性取得:private int com.manu.reflection.bean.User.id
すべての属性取得:private java.lang.String com.manu.reflection.bean.User.name
すべての属性取得:private java.lang.String com.manu.reflection.bean.User.password
----------反射によるクラスのメソッド取得----------
getNameという名前のメソッドm1取得:public java.lang.String com.manu.reflection.bean.User.getName()
setNameという名前のメソッドm2取得:public void com.manu.reflection.bean.User.setName(java.lang.String)
すべてのメソッド取得:public java.lang.String com.manu.reflection.bean.User.toString()
すべてのメソッド取得:public java.lang.String com.manu.reflection.bean.User.getName()
すべてのメソッド取得:public int com.manu.reflection.bean.User.getId()
すべてのメソッド取得:public void com.manu.reflection.bean.User.setName(java.lang.String)
すべてのメソッド取得:public java.lang.String com.manu.reflection.bean.User.getPassword()
すべてのメソッド取得:public void com.manu.reflection.bean.User.setId(int)
すべてのメソッド取得:public void com.manu.reflection.bean.User.setPassword(java.lang.String)

反射によるクラスの情報操作#

Java の反射メカニズムを使用することで、特定のクラスのコンストラクタ、属性、メソッドを取得し、そのクラスに関連する操作を行うことができます。以下は、Java の反射メカニズムを使用してそのクラスのオブジェクトを構築し、そのクラスオブジェクトの属性を操作し、そのクラスオブジェクトのメソッドを呼び出す方法の具体例です:

/**
 * 反射によるクラスの情報操作
 */
private static void setReflectClassInfo() {
	try {
		//特定のクラスのClassオブジェクトを取得
		String name = "com.manu.reflection.bean.User";
		Class clazz = Class.forName(name);
		
		//反射によるクラスのコンストラクタ操作
		System.out.println("----------反射によるクラスのコンストラクタ操作----------");
		Constructor<User> c1 = clazz.getDeclaredConstructor(null); // 無引数コンストラクタ取得
		Constructor<User> c2 = clazz.getDeclaredConstructor(int.class,String.class,String.class); // 有引数コンストラクタ取得
		User u1 = c1.newInstance();
		User u2 = c2.newInstance(1000,"jzman-blog","111111");
		System.out.println("u1:"+u1);
		System.out.println("u2:"+u2);
		
		//反射によるクラスの属性操作
		System.out.println("----------反射によるクラスの属性操作----------");
		User u3 = c1.newInstance();
		Field field = clazz.getDeclaredField("name");
		field.setAccessible(true); // この属性はセキュリティチェックを必要とせず、直接アクセス可能に設定
		field.set(u3, "jzman"); // Userオブジェクトのname属性値を反射で設定
		System.out.println("u3:"+u3);
		System.out.println("Userオブジェクトu3のname属性値取得:"+field.get(u3));
		
		//反射によるクラスのメソッド操作
		System.out.println("----------反射によるクラスのメソッド操作----------");
		User u4 = c1.newInstance();
		Method method = clazz.getDeclaredMethod("setPassword", String.class);
		method.invoke(u4, "222222"); // Userオブジェクトu4のpassword属性を設定、u4.setPassword("222222")と同等
		System.out.println("u4:"+u4);
		
	} catch (Exception e) {
		e.printStackTrace();
	}
}

上記コードの実行結果は以下の通りです:

----------反射によるクラスのコンストラクタ操作----------
u1:User [id=0, name=null, password=null]
u2:User [id=1000, name=jzman-blog, password=111111]
----------反射によるクラスの属性操作----------
u3:User [id=0, name=jzman, password=null]
Userオブジェクトu3のname属性値取得:jzman
----------反射によるクラスのメソッド操作----------
u4:User [id=0, name=null, password=222222]

実際の開発では、特定のコンポーネントで必要な属性が私有である場合が必ずあります。この場合、Java の反射メカニズムを使用することができます。

反射によるジェネリック操作#

Java はジェネリックの消去メカニズムを採用しており、Java のジェネリックはコンパイラ javac の使用のためだけに存在します。これにより、データの安全性を確保し、強制的な型変換の手間を省くことができます。コンパイルが完了すると、すべてのジェネリックに関連する型はすべて消去されます。これらの型を反射で操作できるようにするために、GenericArrayType、ParameterizedType、TypeVariable、WildcardType の 4 種類の型が追加され、Class クラスに帰属できないが原始型と同名の型を表します。つまり、通常は対応する Class オブジェクトを取得できる Type は Class オブジェクトであり、基本データ型です。

反射によるジェネリック操作は Java の Type に関係します。Java における Type はすべての型の共通インターフェースを表し、これらの型には原始型、パラメータ化型、配列型、型変数、基本型が含まれます。宣言は以下の通りです:

/**
 * TypeはJava言語におけるすべての型の共通インターフェース
 * これらの型には原始型、パラメータ化型、配列型、型変数、基本型が含まれます
 */
public interface Type {
    default String getTypeName() {
        return toString();
    }
}

Type には 4 つの直接のサブインターフェースがあり、具体的な意味は以下の通りです:

GenericArrayType:ジェネリック配列型を表します。例:ArraryList<String>[] listArrary
ParameterizedType:パラメータ化型(ジェネリック)を表します。例:ArrayList<String> list
TypeVariable<T>:型変数を表します。例:T
WildcardType:ワイルドカード型を表します。例:?? extends Number、または ? super Integer

以下に GenericArrayType と ParameterizedType の例を示し、反射を使用してジェネリック型を操作する方法を具体的に示します:

// ジェネリックの属性を取得するためのテスト
private String[] array;
private List<String>[] listArray;
// ジェネリックのメソッドを取得するためのテスト
private String testGenericType(Map<String,Integer> map, String[] array, int name,User user) {
    System.out.println("testGenericType");
    return null;
}

// 反射によるジェネリック取得
private static void refectGenericType() {
	try {		
		System.out.println("---------GenericArrayType---------");
		
		// ジェネリック配列(GenericArrayType)を取得
		Field field = ReflectTest02.class.getDeclaredField("listArray"); // 属性listArrayを取得
		GenericArrayType type = (GenericArrayType) field.getGenericType();
		System.out.println("ジェネリック配列取得:"+type);
		
		System.out.println("---------ParameterizedType---------");
		
		// パラメータ化型(ジェネリック)(ParameterizedType)を取得
		Method method = ReflectTest02.class.getDeclaredMethod("testGenericType", Map.class,String[].class,int.class,User.class);
		Type[] types = method.getGenericParameterTypes(); // メソッドのパラメータ型を取得
		for(Type type1: types) {
			System.out.println("メソッドパラメータ型:"+type1);
			if(type1 instanceof ParameterizedType) {
				System.out.println("ParameterizedType:"+type1);
			}
		}
		
	} catch (Exception e) {
		e.printStackTrace();
	}
}

上記コードのメソッド refectGenericType の実行結果は以下の通りです:

---------GenericArrayType---------
ジェネリック配列取得:java.util.List<java.lang.String>[]
---------ParameterizedType---------
メソッドパラメータ型:java.util.Map<java.lang.String, java.lang.Integer>
ParameterizedType:java.util.Map<java.lang.String, java.lang.Integer>
メソッドパラメータ型:class [Ljava.lang.String;
メソッドパラメータ型:int
メソッドパラメータ型:class com.manu.reflection.bean.User

コードを参照して対応する出力結果を確認できます。

反射によるアノテーション情報の取得#

反射を通じてアノテーション情報を取得することもできます。アノテーションに不慣れな場合は、以前に共有した「Java シリーズのアノテーション」を参照してください。ここでは、データベースのテーブルフィールドと Java オブジェクトの属性がどのようにアノテーション情報を通じて対応しているかを簡単に模倣します。なぜデータベースフレームワークを使用する際に、テーブルアノテーションやフィールドアノテーションを通じてテーブルを作成できるのか、ここでは反射を使用してアノテーション情報を取得し、SQL を生成し、対応するテーブルを生成することに関係しています。

テーブルアノテーションとフィールドアノテーションの 2 つのアノテーションを作成し、以下のように参照します:

// テーブルアノテーション
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableAnnotation {
	String value();
}

// フィールドアノテーション
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldAnnotation {
	String column();
	int length();
	String type();
}

次に、クラスを作成し、そのアノテーションを使用します。以下のように参照します:

/**
 * 反射によるアノテーション情報取得テストBean
 * @author jzman
 */

@TableAnnotation(value = "student_table")
public class Student {
	@FieldAnnotation(column = "uid", length = 20, type = "int")
	private int sId;
	@FieldAnnotation(column = "name", length = 10, type = "varchar")
	private String sName;
	@FieldAnnotation(column = "uiaged", length = 3, type = "varchar")
	private int sAge;
	
	// setter、getterメソッド
	//...
}

最後に、アノテーション情報を取得します。以下のように参照します:

/**
 * 反射によるアノテーション情報取得
 * @author jzman
 */
public class ReflectTest03 {
	public static void main(String[] args) {
		try {
			Class clazz = Class.forName("com.manu.reflection.bean.Student");
			
			// 反射によるクラスのアノテーション情報取得
			TableAnnotation tableAnnotation = (TableAnnotation) clazz.getAnnotation(TableAnnotation.class);
			System.out.println("反射によるクラスのアノテーション情報取得:"+tableAnnotation);
			
			// 反射による属性のアノテーション情報取得
			Field field = clazz.getDeclaredField("sName");
			FieldAnnotation fieldAnnotation = field.getAnnotation(FieldAnnotation.class);
			System.out.println("反射による属性のアノテーション情報取得:"+fieldAnnotation);
			
			// 他のアノテーション情報取得方法も同様
			//...
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

上記コードの実行結果は以下の通りです:

反射によるクラスのアノテーション情報取得:@com.manu.reflection.TableAnnotation(value=student_table)
反射による属性のアノテーション情報取得:@com.manu.reflection.FieldAnnotation(column=name, length=10, type=varchar)

明らかに、反射を通じて対応するアノテーション情報が取得されました。これらのアノテーション情報は、クラスに対応するデータベーステーブルのいくつかの重要な情報を示しており、これにより対応する SQL 文を生成できます。これにより、データベーステーブルフィールドと Java オブジェクト属性のマッピング関係を理解することが容易になります。

反射を使用して私有属性や私有メソッドを取得するには、setAccessible を true に設定する必要があります。これにより、Java のセキュリティチェックを回避し、私有の属性やメソッドを取得できます。また、setAccessible を true に設定することで、反射の実行速度をある程度向上させることができます。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。