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[] mathods = clazz.getDeclaredMethods();
		for(Method m: mathods) {
			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 に設定することで、反射の実行速度をある程度向上させることができます。

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