リフレクションでJava Genericsで指定された型パラメータを取得する

前置き

List<T>への項目追加時に自動的に型変換する仕組みが作りたかったため、 作る方法がないか調べてみた。 総称型の具象化された型パラメータを扱うためのjava.lang.reflectの機能の説明になる。

JavaのGenericsはObject型をObject型以外の型として 透過的に扱える仕組みであるため、 実行時にオブジェクトから型パラメータを取得できない。(.NETはできる)

しかし、クラスのフィールドやメソッドからは具象化された型パラメータの型情報を取得できる。

総称型にも対応するType型

JDK 1.5以降、Class型に対して総称型を扱うランタイム情報を持つ、Type型が導入されている。 この型を実装するインターフェースは下記となる。

Class               --- Classオブジェクトも継承している。
GenericArrayType    --- 総称配列型 (T)
ParameterizedType   --- 総称型(List<T>等)
WildcardType        --- ワイルドカード(?)

Listの型パラメータの取得はParameterizedTypeのgetActualTypeArguments()を使えばできる。

Type型取得方法

java.lang.reflectでは下記の方法でTypeオブジェクトを取得できる。

Field field = ....;

Type type = field.getGenericType();

Method method = ...;

Type type = method.getGenericReturnType();
Type types = method.getGenericParameterTypes();

型定義と取得されるType型の構造サンプル

下記に各種の型定義に対して、どのようなType型が取得されるかを検証するコードとその実行結果を記載する。

package net.dachicraft.toolkit.reflect;

import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class GenericTest<T> 
{
    public List<String> objs1;
	public Object objs2;
	public List<List<String>> objs3;
	public T objs4;
	public T objs5;
		
	public static void main(String args) throws Exception
	{
		Type type;
		
		Field field1 = GenericTest.class.getField("objs1");
		System.out.println("[objs1]");
		type = field1.getGenericType();
		System.out.println(type + " : " + type.getClass());
		
		ParameterizedType paramType = (ParameterizedType)field1.getGenericType();
		for(Type i : paramType.getActualTypeArguments()) {
			System.out.println(i + " : " + i.getClass());
		}
		
		Field field2 = GenericTest.class.getField("objs2");
		System.out.println("[objs2]");
		type = field2.getGenericType();
		System.out.println(type + " : " + type.getClass());
		
		
		Field field3 = GenericTest.class.getField("objs3");
		System.out.println("[objs3]");
		type = field3.getGenericType();
		System.out.println(type + " : " + type.getClass());
		
		type = ( (ParameterizedType)type).getActualTypeArguments()[0];
		System.out.println(type + " : " + type.getClass());
		type = ( (ParameterizedType)type).getActualTypeArguments()[0];
		System.out.println(type + " : " + type.getClass());
		
		
		Field field4 = GenericTest.class.getField("objs4");
		System.out.println("[objs4]");
		type = field4.getGenericType();
		System.out.println(type + " : " + type.getClass());

		Field field5 = GenericTest.class.getField("objs5");
		System.out.println("[objs5]");
		type = field5.getGenericType();
		System.out.println(type + " : " + type.getClass());

		type = ( (GenericArrayType)type).getGenericComponentType();
		System.out.println(type + " : " + type.getClass());
	}
}

実行結果

[objs1]
java.util.List<java.lang.String> : class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
class java.lang.String : class java.lang.Class
[objs2]
class java.lang.Object : class java.lang.Class
[objs3]
java.util.List<java.util.List<java.lang.String>> : class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
java.util.List<java.lang.String> : class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
class java.lang.String : class java.lang.Class
[objs4]
T : class sun.reflect.generics.reflectiveObjects.TypeVariableImpl
[objs5]
T[] : class sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl
T : class sun.reflect.generics.reflectiveObjects.TypeVariableImpl

objs3のList<List<String>>の定義ではParameterizedTypeが入れ子になっているのが分かる。