S-JIS[2004-06-12/2015-04-26] 変更履歴

リフレクション

Javaでは、クラス生成やメソッド呼び出しをソース上に直接書いてコンパイル時に決定されるだけでなく、文字列(クラス名)を使ってクラスを生成したり、 メソッド名の文字列を使ってメソッドを呼び出したりすることが出来る。


クラス生成

Javaでは、クラスを扱う為にjava.lang.Classというクラスが用意されている。
java.langパッケージなので、importは不要)

どんなクラスでも、「クラス名.class」という式でClassインスタンスを取得することが出来る。

JDK1.4以前
Class clazz = クラス名.class;
Class strClass = String.class;
Class intClass = int.class;      //プリミティブ型もOK!
Class arrClass = String[].class; //配列もOK
Class voidClass= void.class;     //voidも書ける
JDK1.5以降
Class<クラス名> clazz = クラス名.class;
Class<String> strClass = String.class;
Class<Integer> intClass = int.class;
Class<String[]> arrClass = String[].class;
Class<Void> voidClass = void.class;
//このような書き方も可
Class<? extends クラス名> clazz = クラス名.class;
Class<? extends String> strClass = String.class;
Class<? extends Integer> intClass = int.class;
Class<? extends String[]> arrClass = String[].class;
Class<? extends Void> voidClass = void.class;

また、どんなインスタンスでも、Object#getClass()を使ってClassインスタンスを取得できる。

JDK1.4以前
クラス obj = 〜;
Class clazz = obj.getClass();
 
JDK1.5以降
クラス obj = 〜;
Class<? extends クラス> clazz = obj.getClass();
getClass()の戻り型は、実はジェネリクスの使い方としてはちょっと特殊

そして、任意のクラス名の文字列からClassインスタンスを取得するには、以下のようにする
(クラス名はFQCNパッケージ名.クラス名)で指定する。
 デフォルトパッケージのクラスは、パッケージ部抜きでそのままクラス名を記述すればよい。[2010-01-15]

JDK1.4以前
Class clazz = Class.forName("クラス名");
Class clazz;
try {
	clazz = Class.forName("java.lang.String");
} catch (ClassNotFoundException e) {
	throw new RuntimeException(e);
}
JDK1.5以降
[/2015-04-26]
Class<?> clazz = Class.forName("クラス名");
Class<?> clazz;
try {
	clazz = Class.forName("java.lang.String");
} catch (ClassNotFoundException e) {
	throw new RuntimeException(e);
}
@SuppressWarnings("unchecked")
Class<クラス> clazz = (Class<クラス>)Class.forName("クラス名");
Class<String> clazz = getClassForName("java.lang.String");
@SuppressWarnings("unchecked")
public static <T> Class<T> getClassForName(String className) {
	try {
		return (Class<T>) Class.forName(className);
	} catch (ClassNotFoundException e) {
		throw new RuntimeException(e);
	}
}
Class<? extends クラス> clazz = Class.forName("クラス名")
	.asSubclass(クラス.class);
Class<? extends String> clazz;
try {
	clazz = Class.forName("java.lang.String").asSubclass(String.class);
} catch (ClassNotFoundException e) {
	throw new RuntimeException(e);
}

クラスローダーを使って任意のクラス名の文字列からClassインスタンスを取得する例。[2010-01-15]

	// クラスローダーの取得例
	ClassLoader loader = Thread.currentThread().getContextClassLoader();
	ClassLoader loader = ClassLoader.getSystemClassLoader()
	ClassLoader loader = URLClassLoader.newInstance(new URL[]{ new File("jarファイル").toURI().toURL() });
JDK1.4以前
Class clazz = loader.loadClass("クラス名");
Class clazz;
try {
	clazz = loader.loadClass("java.lang.String");
} catch (ClassNotFoundException e) {
	throw new RuntimeException(e);
}
JDK1.5以降
Class<?> clazz = loader.loadClass("クラス名");
Class<?> clazz;
try {
	clazz = loader.loadClass("java.lang.String");
} catch (ClassNotFoundException e) {
	throw new RuntimeException(e);
}

初めて「Classインスタンスを取得」される際には、「クラスがロード」される。[2010-01-15]
すなわち、そのクラスのstaticフィールドの初期化(および静的初期化子の実行)が行われる。


java.lang.Classには、クラスの情報を扱うメソッドが色々ある。

メソッド名 戻り型 説明 更新日
getPackage() String パッケージ名を返す。  
getName() String クラス名(パッケージ付き。つまり限定名(FQN))を返す。 2007-03-02
getSimpleName() String クラス名(パッケージ無し。つまり単純名)を返す。JDK1.5以降 2007-03-02
getCanonicalName() String クラス名(正準名)を返す。JDK1.5以降 2008-09-13
getTypeName() String クラス名を返す。配列のとき以外はgetName()と同じ。JDK1.8以降 2014-03-19
asSubclass(Class<U> clazz) Class<? extends U> Uのクラス(Class<? extends U>)を返す。JDK1.5以降
保持しているクラスがUにキャストできない時はClassCastExceptionが発生する。
2015-04-26
getSuperclass() Class 親クラスを返す。 2014-03-19
getAnnotatedSuperclass() AnnotatedType JDK1.8以降 2014-03-19
getInterfaces() Class[] 自分が直接実装しているインターフェースを返す。
(スーパークラスで実装しているインターフェースは対象外)
 
getAnnotatedInterfaces() AnnotatedType[] JDK1.8以降 2014-03-19
getConstructors() Constructor[] publicなコンストラクターを返す。  
getConstructor(引数の型の配列)
getConstructor(引数の型, …)
Constructor 引数ありコンストラクターを返す。(引数の種類が合致するpublicなもの)  
getDeclaredFields() Field[] 自分が直接宣言している全ての変数を返す。  
getFields() Field[] 自分が扱えるpublicな変数を返す。(スーパークラスの分も含む)  
getField("フィールド名") Field フィールドを返す。  
getDeclaredField("フィールド名") Field フィールドを返す。(privateなフィールドも可→アクセス方法 2007-09-10
getDeclaredMethods() Method[] 自分が直接宣言している全てのメソッドを返す。  
getMethods() Method[] 自分が扱えるpublicなメソッドを返す。(スーパークラスの分も含む) 2011-09-22
getMethod("メソッド名", 引数の型の配列) Method メソッドを返す。(引数の種類が合致するpublicなもの)  
getDeclaredMethod("メソッド名", 引数の型の配列)
getDeclaredMethod("メソッド名", 引数の型, …)
Method メソッドを返す。(privateなメソッドも可→呼び出し方法 2007-09-10
getAnnotationsByType(クラス) A[] 指定されたクラスのアノテーションを返す。JDK1.8以降
複数指定されたアノテーションでも直接取得できる。
2014-03-20
getAnnotations() Annotation[] アノテーションを返す。JDK1.5以降 2007-11-10
getDeclaredAnnotations() Annotation[] アノテーション(直接指定しているもの)を返す。JDK1.5以降 2007-11-10
getDeclaredAnnotation(クラス) A 指定されたクラスのアノテーションを返す。JDK1.8以降 2014-03-19
getDeclaredAnnotationsByType(クラス) A[] 指定されたクラスのアノテーションを返す。JDK1.8以降
複数指定されたアノテーションでも直接取得できる。
2014-03-20
getEnumConstants() T[] 列挙型の場合、列挙子一覧を返す。valuesメソッド相当。JDK1.5以降
(列挙型でない場合はnullが返る)
2015-04-16
equals(obj) boolean Class#equals()は特にオーバーライドされていないので、
Object#equals()そのもの。
すなわち「this==obj」なので、equals()を使わずに
直接「==演算子」を用いて比較してよい。
2010-01-25
toGenericString() String toString()に修飾情報(可視性等)も加えた文字列を返す。JDK1.8以降 2014-03-19

あるオブジェクトあるクラスサブクラスであるかどうか(継承しているかどうか)は、以下のようにして判定する。[2007-02-16]

	if (obj instanceof クラス) 〜;
	if (クラス.class.isInstance(obj)) 〜;
	if (クラス.class.isAssignableFrom(obj.getClass()) 〜;

引数なしコンストラクターを使ったインスタンス生成

リフレクションを使ってインスタンスを生成するにはClass#newInstance()を使う。
(引数無しのコンストラクターを使ってインスタンスが生成される。引数ありコンストラクターを使う方法は後述

JDK1.4以前
Class clazz = 〜;
Object obj = clazz.newInstance();
//     obj = new クラス(); と同じ
Class clazz = String.class;

String obj;
try {
	obj = (String)clazz.newInstance();
} catch (InstantiationException e) {
	throw new RuntimeException(e);
} catch (IllegalAccessException e) {
	throw new RuntimeException(e);
}
newInstance()の戻り型はObjectなので、そのオブジェクトを使う際には 必要な型にキャストする必要がある。
JDK1.5以降
Class<クラス> clazz = 〜;
Class<? extends クラス> clazz = 〜;
クラス obj = clazz.newInstance();
//     obj = new クラス(); と同じ
Class<String> clazz = String.class;

String obj;
try {
	obj = clazz.newInstance();
} catch (InstantiationException e) {
	throw new RuntimeException(e);
} catch (IllegalAccessException e) {
	throw new RuntimeException(e);
}
Classに型引数が指定されている場合、newInstance()はその型を返すようにコンパイルされるので、キャストは不要。[2010-01-15]
JDK1.7以降
Class<String> clazz = String.class;

String obj;
try {
	obj = clazz.newInstance();
} catch (ReflectiveOperationException e) {
	throw new RuntimeException(e);
}
JDK1.7で、リフレクション系の例外の共通クラスが定義された。[2013-08-06]

配列のインスタンス生成


引数ありコンストラクターを使ったインスタンス生成

引数ありコンストラクターを使ってインスタンスを生成するには、一旦コンストラクターを取得し、そのnewInstance()を使う。

getConstructor()にはClassの配列を渡す。これが、コンストラクターの引数の型の種類を表す。
newInstance()にはObjectの配列を渡す。これが、コンストラクターの引数の値を表す。
引数がプリミティブ型(int等)の場合、該当するラッパークラス(Integer等)を使う。

 
import java.lang.reflect.Constructor;
 
JDK1.4以前
Class clazz = 〜;
Class[] types = 引数の型の配列;
Constructor constructor = clazz.getConstructor(types);

Object[] args = 引数の値の配列;
Object obj = constructor.newInstance(args);
//     obj = new クラス(引数,…); と同じ
Class clazz = Integer.class;

Class[] types = { int.class };
Constructor constructor;
try {
	constructor = clazz.getConstructor(types);
} catch (SecurityException e) {
	throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
	throw new RuntimeException(e);
}

Object[] args = { new Integer(123) };
Object obj;
try {
	obj = constructor.newInstance(args);
} catch (IllegalArgumentException e) {
	throw new RuntimeException(e);
} catch (InstantiationException e) {
	throw new RuntimeException(e);
} catch (IllegalAccessException e) {
	throw new RuntimeException(e);
} catch (InvocationTargetException e) {
	throw new RuntimeException(e);
}
 
JDK1.5以降
Class<クラス> clazz = 〜;
Class<? extends クラス> clazz = 〜;
Class<?>[] types = 引数の型の配列;
Constructor<クラス> constructor = clazz.getConstructor(types);

Object[] args = 引数の値の配列;
クラス obj = constructor.newInstance(args);
//     obj = new クラス(引数,…); と同じ
Class<Integer> clazz = Integer.class;

Class<?>[] types = { int.class };
Constructor<Integer> constructor;
try {
	constructor = clazz.getConstructor(types);
} catch (SecurityException e) {
	throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
	throw new RuntimeException(e);
}

Object[] args = { Integer.valueOf(123) };
Integer obj;
try {
	obj = constructor.newInstance(args);
} catch (IllegalArgumentException e) {
	throw new RuntimeException(e);
} catch (InstantiationException e) {
	throw new RuntimeException(e);
} catch (IllegalAccessException e) {
	throw new RuntimeException(e);
} catch (InvocationTargetException e) {
	throw new RuntimeException(e);
}
ConstructorにもClassと同じ型を指定する。[2010-01-15]
JDK1.7以降
Class<Integer> clazz = Integer.class;

Class<?>[] types = { int.class };
Constructor<Integer> constructor;
try {
	constructor = clazz.getConstructor(types);
} catch (SecurityException |
         NoSuchMethodException e) {
	throw new RuntimeException(e);
}

Object[] args = { Integer.valueOf(123) };
Integer obj;
try {
	obj = constructor.newInstance(args);
} catch (IllegalArgumentException |
         ReflectiveOperationException e) {
	throw new RuntimeException(e);
}

 

JDK1.7で、リフレクション系の例外の共通クラスが定義された。[2013-08-06]

JDK1.5からはコンストラクター取得やインスタンス生成メソッドの引数が可変長引数に改められたので、もっと簡単に指定できるようになった。[2007-11-13]
可変長引数なので、もちろん今まで通りの配列の方法も使える。

	Constructor ct = clazz.getConstructor(int.class, int.class);
	Object obj = ct.newInstance(123, 456);	//自動ボクシングも使用

ただし、「引数なし」を意味するnullを指定していた場合は注意[2008-07-03]


総称型による限定

ClassConstructorはどのクラスも扱うわけだが、JDK1.5から総称型が導入され、特定のクラスを指し示すことも出来るようになった。[2007-05-02]

JDK1.4までは、Classを使ってインスタンスを生成する場合、目的のクラスへはキャストしてやる必要があった。
「特定のクラスである」と限定している場合は、総称型を使って明示することが出来る。

public static ArrayList newArrayList(Class c) {
	try {
		return (ArrayList)c.newInstance();		//JDK1.4まではキャストが必須
	} catch (Exception e) {
		throw new RuntimeException(e);
	}
}

↓総称型でArrayListクラスのみを受け付ける

public static ArrayList newArrayList(Class<ArrayList> c) {
	try {
		return c.newInstance();			//キャストが不要
	} catch (Exception e) {
		throw new RuntimeException(e);
	}
}
	List l = newArrayList(ArrayList.class);		//ArrayList以外はコンパイルエラーとなる
	//×     newArrayList(l.getClass());
	//×     newArrayList(new ArrayList().getClass());

Object#getClass()は特定のClassを返すようには出来ていないようだ。当たり前か。


特定の派生クラスのみを受け付ける指定方法もある。

public static List newList(Class<? extends List> c) {	//Listの派生クラスのClassのみ受け入れる
	try {
		return c.newInstance();
	} catch (Exception e) {
		throw new RuntimeException(e);
	}
}
	List al = newList(ArrayList.class);
	List gl = newList(new ArrayList().getClass());
	List nl = newList(List.class);	//コンパイルは通る(Listは直接インスタンス化できないのでnewList()の中で実行時エラーになる)

内部クラスのインスタンス生成

リフレクションで(static無しの)内部クラスのインスタンスを生成する方法。[2007-02-09]

内部クラスの通常のインスタンス生成の形式はクラスX内部クラスAに対して「x.new A()」だが、いわば「new A(x)」とでも言うべき暗黙の引数が存在しているようだ。
そういうコンストラクターが存在している事は、クラスのコンパイル後の定義を見てみると分かる。 (逆に「引数なしコンストラクター」は存在していない…ソース上は引数無しであっても!)

という訳で、内部クラスをリフレクションを使ってインスタンス化するには、引数ありコンストラクターと同じ方法を用いる。

	X x = new X();
	Class cl = X.A.class;
	Constructor con = cl.getConstructor(new Class[]{ X.class });
	X.A a = (X.A)con.newInstance(new Object[]{ x });

なお、内部クラスかどうかはClass#isMemberClass()で判断できる。[2008-02-10]


メソッド呼び出し

Javaでは、メソッドを保持するためにMethodというクラスが用意されている。

import java.lang.reflect.Method;

Methodは、Class#getMethod()getDeclaredMethod()を使って取得できる。
メソッド呼び出しにはMethod#invoke()を使用する。
戻り値はObject型として返るが、内容は当然元のメソッドが返す型となる。プリミティブ型(int等)の場合、ラッパークラス(Integer等)として返る。またvoidの場合はnullが返る。
(何の型が返るのかはMethod#getReturnType()で調べる)

引数なしstaticメソッド実行

	Method method;
	try {
		method = clazz.getMethod("メソッド名", null);
	} catch (SecurityException e) {
		throw new RuntimeException(e);
	} catch (NoSuchMethodException e) {
		throw new RuntimeException(e);
	}

	Object ret; //戻り値
	try {
		ret = method.invoke(null, null);
		//  = クラス名.メソッド名(); と同じ
	} catch (IllegalArgumentException e) {
		throw new RuntimeException(e);
	} catch (IllegalAccessException e) {
		throw new RuntimeException(e);
	} catch (InvocationTargetException e) {
		throw new RuntimeException(e);
	}

引数なしのインスタンスメソッド実行

	Method method;
	try {
		method = object.getClass().getMethod("メソッド名", null);
	} catch (SecurityException e) {
		throw new RuntimeException(e);
	} catch (NoSuchMethodException e) {
		throw new RuntimeException(e);
	}

	Object ret; //戻り値
	try {
		ret = method.invoke(object, null);
		//  = object.メソッド名(); と同じ
	} catch (IllegalArgumentException e) {
		throw new RuntimeException(e);
	} catch (IllegalAccessException e) {
		throw new RuntimeException(e);
	} catch (InvocationTargetException e) {
		throw new RuntimeException(e);
	}

引数ありのインスタンスメソッド実行

	Method method;
	try {
		method = object.getClass().getMethod("メソッド名", new Class[]{ int.class });
	} catch (SecurityException e) {
		throw new RuntimeException(e);
	} catch (NoSuchMethodException e) {
		throw new RuntimeException(e);
	}

	Object ret; //戻り値
	try {
		ret = method.invoke(object, new Object[]{ new Integer(1) });
		//  = object.メソッド((int)1); と同じ
	} catch (IllegalArgumentException e) {
		throw new RuntimeException(e);
	} catch (IllegalAccessException e) {
		throw new RuntimeException(e);
	} catch (InvocationTargetException e) {
		throw new RuntimeException(e);
	}

すなわち、invoke()の第1引数が有ればそのインスタンスに対して実行、無ければ(nullならば)staticメソッドとして実行する。
第2引数でメソッド自身の引数を指定する。引数が無い場合、空の配列かnullを渡す。引数の扱い方については、コンストラクターの場合と同様。

呼び出したメソッドが返す例外は、invoke()の中でInvocationTargetExceptionに入れられる。


JDK1.5では、インスタンス生成と同様に、メソッドの呼び出しも可変長引数になって便利になった。[2007-11-13]

	public Object メソッド名(int arg1, int arg2) { 〜 }
	public Object 引数無しメソッド() { 〜 }
	Object ret, ret2;
	try {
		Method m = clazz.getMethod("メソッド名", int.class, int.class);
		ret = m.invoke(object, 123, 456); //自動ボクシングも使用

		Method m2 = clazz.getMethod("引数無しメソッド");
		ret2 = m2.invoke(object);
	} catch (Exception e) {
		throw new RuntimeException(e);
	}

ただし、「引数なし」を意味するnullを指定していた場合は注意[2008-07-03]


可変長引数ありのメソッド

可変長引数を持つメソッドをgetMethod()で取得したい場合、引数の型には配列を指定する。[2008-02-10]

	// リフレクションで取得したい可変長引数メソッド
	public void メソッド名(クラス... args) { 〜 }
		Method m = clazz.getMethod("メソッド名", クラス[].class);

		m.invoke(オブジェクト, new Object[]{ new クラス[]{ 値,値,… }});
//×		m.invoke(オブジェクト, 値,値,…);

invoke()の引数に(配列を使わずに)実引数を並べるような書き方は出来ない。
要するに普通に配列を扱うのと同様にコーディングする。

具体例 [2015-04-26]
  可変長引数以外の引数が無い 可変長引数以外の引数が有る
取得したいメソッド public String example1(String... ss) {
  〜
}
public String example2(String s, String... ss) {
  〜
}
取得方法 Method m = clazz.getDeclaredMethod("example1", String[].class); Method m = clazz.getDeclaredMethod("example2", String.class, String[].class);
呼び出し方法
(invokeに配列を渡す)
Object[] args = { new String[]{ "abc", "def" } };
Object r = m.invoke(object, args);
Object[] args = { "123", new String[]{ "abc", "def" } };
Object r = m.invoke(object, args);
呼び出し方法
(invokeの可変長引数を利用)
以下の書き方では実行時に例外発生。
Object r = m.invoke(object, "abc", "def");
以下の書き方では実行時に例外発生。
Object r = m.invoke(object, "123", "abc", "def");
以下の書き方ではコンパイル時に警告、実行時に例外発生。
Object r = m.invoke(object, new String[]{ "abc", "def" });
Object r = m.invoke(object, "123", new String[]{ "abc", "def" });
以下の書き方ならOK。
Object r = m.invoke(object, (Object)new String[]{ "abc", "def" });
Object r = m.invoke(object, (Object)"123", (Object)new String[]{ "abc", "def" });

「可変長引数しか持たないメソッド」を呼び出す際にinvokeの可変長引数を利用した書き方が出来ない理由は、以下のように解釈されるから。[2015-04-26]

m.invoke(object, "abc", "def") m.invoke(object, new String[]{ "abc", "def" }) m.invoke(object, (Object)new String[]{ "abc", "def" })
解釈 1個目の引数の型はString、値は"abc"
2個目の引数の型はString、値は"def"
しかし呼び出したいメソッドの引数は1個のみで、型はString[]
なので一致しない。
invokeの引数の型はObject...であり、実体はObject[]。
Javaの配列は共変なので、String[]はObject[]に代入可能。
したがってm.invoke(object, new Object[]{ "abc", "def" })と解釈される。(ただし警告が出る)
これは左記のm.invoke(object, "abc", "def")と同じ。
String[]をObjectにキャストしているので、
invoke側から見ると、1個の引数という扱い。

要するに、invokeに配列を渡すと、その配列が「invokeの引数Object[]そのもの」なのか「Object[0]に配列を入れた」のか区別が付かない(というか前者として解釈される)のが問題。
なので、呼び出したいメソッドに可変長引数以外の引数が1つでもあれば、「invokeのObject[]」と「呼び出したいメソッドの引数」との区別が付く。


ちなみに、あるメソッドが可変引数を持つかどうかはMethod#isVarArgs()で判断できる。[2007-11-13]


メソッドの引数

Methodクラスからメソッドの引数の情報を取得することが出来る。[2014-03-20]
(同様に、Constructorクラスからコンストラクターの引数の情報を取得できる)

Method・Constructorクラスの引数関連メソッド
メソッド名 戻り型 説明
getParameterCount() 1.8 int 引数の個数を返す。
getParameterTypes() Class<?>[] 引数の型(引数の個数分の配列)を返す。
getParameterAnnotations() 1.5 Annotation[][] 引数に付けられたアノテーション(引数の個数分の配列)を返す。
getParameters() 1.8 Parameter[] 引数の一覧を返す。

ParameterクラスはJDK1.8で追加されたクラスで、引数1個分の情報を持つ。[2014-03-20]

import java.lang.reflect.Parameter;
Parameterクラスの主なメソッド
メソッド名 戻り型 説明
isNamePresent() boolean コンパイルされたclassファイル内に引数名を保持しているかどうかを返す。
trueの場合、ソース上に書かれていた引数名をgetName()で取得することが出来る。
classファイル内に引数名を保持させる為には、javacに-parametersオプションを付けてコンパイルする必要がある。
(Eclipseの場合はプロジェクトのプロパティーから「Java Compiler」を選び、「Store method parameter names (usable via reflection)」にチェックを付ける)
参考: irofさんのJava8で実行時にメソッドの引数の名前がとれるぽい
getName() String 引数名を返す。
classファイル内で引数名を保持していない場合、「arg引数番号」という文字列が返る。
getDeclaringExecutable() Executable その引数を持っているMethodあるいはConstructorを返す。
getModifiers() int 属性情報を返す。
getParameterizedType() Type 引数の型を返す。
getType() Class<?> 引数のクラスを返す。
getAnnotatedType() AnnotatedType  
isImplicit() boolean 暗黙に作られた引数の場合にtrueを返す?
普通のメソッドはfalseを返す。
内部クラスのコンストラクターの第1引数(暗黙に外側クラスのオブジェクトを渡す)でもfalseを返すようだ。
isSynthetic() boolean 合成された場合にtrueを返す。
isVarArgs() boolean 可変長引数の場合にtrueを返す。
getAnnotation(クラス) A 引数に付けられているアノテーションを返す。
getAnnotationsByType(クラス) A[] 引数に付けられているアノテーションを返す。
getDeclaredAnnotations() Annotation[] 引数に付けられているアノテーションを返す。
getDeclaredAnnotation(クラス) A getAnnotation()と同じ。
getDeclaredAnnotationsByType(クラス) A[] getAnnotationsByType()と同じ。
getAnnotations() Annotation[] getDeclaredAnnotations()と同じ。

フィールド操作

フィールド(メンバー変数)をリフレクションで扱うのは、メソッドよりも簡単。[2007-02-14/2007-11-15]

import java.lang.reflect.Field;

フィールドの取得

	Class c = obj.getClass();
	Field f = c.getField("field");	//フィールド名を指定

フィールドに値をセット

	f.set(obj, new Integer(123));	//プリミティブ型ラッパークラスを使用するが
	f.setInt(obj, 123);		//専用のメソッドもある

	f.set(obj, 123);			//JDK1.5(自動ボクシングを使用

フィールドの値を取得

	Integer n = (Integer)f.get(obj);
	int n = f.getInt(obj);

	int n = (Integer)f.get(obj);	//JDK1.5(自動ボクシングを使用

staticフィールドアクセス

staticフィールドへのアクセスは、メソッドと同じく、対象オブジェクトにnullを指定する。

	Object value = f.get(null);
	f.set(null, value);

例外捕捉の記述の簡易化

リフレクションでは発生しうる例外がいくつもある為、catch節の個数が多くなる。[2012-04-08]
いちいちcatchを書くのが面倒なので、つい「Exception」でキャッチしてしまいたくなるが、これは望ましくない。

JDK1.7では例外のマルチキャッチが出来るようになったので、個別に例外を書いてもcatch節は1つでよくなった。

	Object ret;
	try {
		Method m = clazz.getMethod("メソッド名", int.class, int.class);
		ret = m.invoke(null, 123, 456);
	} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
		throw new RuntimeException(e);
	}

また、JDK1.7ではリフレクション系の例外は共通の親クラスが1つ定義されたので、それを使うともっとシンプルになる。

	Object ret;
	try {
		Method m = clazz.getMethod("メソッド名", int.class, int.class);
		ret = m.invoke(null, 123, 456);
	} catch (IllegalArgumentException | SecurityException | ReflectiveOperationException e) {
		throw new RuntimeException(e);
	}

配列

配列をリフレクションで扱うには、専用のArrayクラスを使用する。[2007-02-13]

import java.lang.reflect.Array;

オブジェクトの配列判断

オブジェクトが配列なのかどうかはClass#isArray()を使用して判定する。

	if (obj.getClass().isArray()) {
		// objは配列
	}

配列の型(クラス)はClass#getComponentType()で取得する。[2010-02-01]
(配列でない場合はnullが返る)

	Class<?> type =  obj.getClass().getComponentType();

配列の長さ

配列の長さ(個数)(arr.length)を取得するには、以下のようにする。

	int length = Array.getLength(arr);

要素取得

配列の要素を取得する(Type a = arr[index];)には、以下のようにする。

	Type a = (Type)Array.get(arr, index);

プリミティブ型で型の種類が分かっている場合は、それに応じたメソッドを使うのが便利。

	int n = Array.getInt(int_arr, index);

値設定

配列に値をセットする(arr[index] = val;)には、以下のようにする。

	Array.set(arr, index, val);
	Array.setInt(int_arr, index, n);

新規配列の作成

あるクラスの配列を作る場合(arr = new 要素クラス[配列数];)は、以下のようにする。

	Object arr = Array.newInstance(要素クラス.class, 配列数);

多次元配列作成用(arr = new long[2][3];)のメソッドもある。[2007-02-14]

	Object arr = Array.newInstance(long.class, new int[]{ 2, 3 });

JDK1.6では、これも可変長引数に改められた。[2008-02-10]

	Object arr = Array.newInstance(long.class, 2, 3);
※newInstance()の戻り値の型がジェネリクスになっていないのは、プリミティブ型の配列を上手く表現できない為らしい。[2010-02-13]
 →IBMのJavaの理論と実践: Generics、了解!
public static <T> T[] newArray(Class<T> c) { return (T[]) Array.newInstance(c, 10); }

	int[] i = newArray(int.class);	//コンパイルエラー「Integer[]からint[]に変換できない」
					//型引数にはプリミティブ型は指定できず、ラッパークラスになるから。

ちなみに配列のダンプ(Arrays.deepToString())の引数は明示的な配列でないとコンパイルエラーになるので、単純にキャストしてやる。

	System.out.println(Arrays.deepToString((Object[]) arr));

JavaBean

リフレクションの範疇に入るかどうかは怪しいが、JavaBeansのsetter/getterメソッドを扱うPropertyDescriptorというクラスがある。[2007-12-03]

JavaBeanSample.java:

public class JavaBeanSample {
	private String str;

	public void setData(String s) {
		this.str = s;
	}

	public String getData() {
		return str;
	}
}

JavaBeanのサンプルなので、publicクラス、publicなデフォルトコンストラクター(引数なしのコンストラクター。上記の例では暗黙の定義)、プロパティー名(上記の例では「data」)にset/getの接頭辞を付けたpublicメソッドを用意した。

import java.beans.PropertyDescriptor;
		JavaBeanSample bean = new JavaBeanSample();

		PropertyDescriptor pd = new PropertyDescriptor("data", JavaBeanSample.class);

		//セッターメソッドを取得・実行
		Method w = pd.getWriteMethod();
		w.invoke(bean, new Object[] { "abc" });

		//ゲッターメソッドを取得・実行
		Method r = pd.getReadMethod();
		String s = (String) r.invoke(bean, (Object[])null);
		System.out.println(s);

PropertyDescriptorのコンストラクターにプロパティー名とクラスを渡すと、そのプロパティーに関する情報が生成される。
この際、そのプロパティーのsetter/getterメソッドが両方とも見つからないとエラーになる(例外が発生する)(片方だけでは駄目)。

JDK1.4.2_07でStrutsの動作が変わったのは、これが関係していたのかなぁ…?

commons BeanUtils


クラス・メンバーの属性

クラスやメンバー(メソッドフィールド)の属性(修飾子)は、Modifierクラスで判断する。[2007-02-14]
属性というのは、staticであるとかfinalであるとかpublicであるとか。

import java.lang.reflect.Modifier;
	int mod = clazz .getModifiers();	//クラスの属性取得
	//  mod = method.getModifiers();	//メソッドの属性取得
	//  mod = field .getModifiers();	//フィールドの属性取得
	//  mod = param .getModifiers();	//引数の属性取得
	if (Modifier.isStatic(mod)) {
		//static属性
	}
	//属性の文字列表記
	System.out.println(Modifier.toString(mod));
判断内容 判断方法 Class Accessible
Object
Member Const
ructor
Method Field 更新日
プリミティブ型かどうか isPrimitive()           2008-02-10
インターフェースかどうか isInterface()           2008-02-10
配列かどうか isArray()           2008-02-10
匿名クラスかどうか isAnonymousClass() 1.5           2010-02-01
局所クラスかどうか isLocalClass() 1.5           2010-02-01
メンバークラス(内部クラス)かどうか isMemberClass() 1.5           2008-02-10
列挙型かどうか isEnum() 1.5           2008-02-10
アノテーションかどうか isAnnotation() 1.5           2010-02-01
アノテーションの有無 isAnnotationPresent(アノテーションのクラス) 1.5   2008-02-10
アクセス可能かどうか isAccessible()     2008-02-10
合成されたのかどうか isSynthetic() 1.5   2008-02-10
デフォルトメソッドかどうか isDefault() 1.8           2014-03-20
publicかどうか Modifier.isPublic(mod)   2008-02-10
privateかどうか Modifier.isPrivate(mod) 2008-02-10
protectedかどうか Modifier.isProtected(mod) 2008-02-10
package privateかどうか (mod & (Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE)) == 0 2008-02-10
staticかどうか Modifier.isStatic(mod) 2007-02-14
finalかどうか Modifier.isFinal(mod) 2008-02-10
synchronizedされているか Modifier.isSynchronized(mod) 2008-02-10
volatileかどうか Modifier.isVolatile(mod) 2008-02-10
transientかどうか Modifier.isTransient(mod) 2008-02-10
nativeかどうか Modifier.isNative(mod) 2008-02-10
インターフェースかどうか Modifier.isInterface(mod) 2008-02-10
抽象かどうか Modifier.isAbstract(mod) 2008-02-10
strictfpかどうか Modifier.isStrict(mod) 2010-02-01
配列の型 getComponentType()           2010-02-01
メソッドの戻り値の型 getReturnType()           2007-06-20
throwsで宣言されている例外 getExceptionTypes()         2008-02-10
可変数の引数を持つかどうか isVarArgs() 1.5         2008-02-10
橋渡しメソッドかどうか isBridge() 1.5           2008-02-10
フィールドの型 getType()           2008-02-10
列挙型定数かどうか isEnumConstant() 1.5           2008-02-10

メソッドの戻り型

メソッドの戻り値の型はMethod#getReturnType()で取得できる。[2010-01-25]

この値の型はClass。
つまり、Stringを返すメソッドの場合、getReturnType()の戻り値は「String.class」と等しい。

戻り値の型がプリミティブ型の場合、ラッパークラスに定義されているプリミティブ用のクラスが返る。
例えばintの場合、Integer.TYPEと等しい。 もしくは「int.class」でも同じ。

voidの場合はVoid.TYPE(java.lang.Void)が返る。 もしくは「void.class」でも同じ。

なお、Class#equals(other)は「this==other」と同じなので、equals()メソッドでなく「==演算子」で比較してよい。


privateメンバーのアクセス

リフレクションでは、privateなメソッドやフィールドも(条件付きで)アクセスすることが出来る。[2007-09-10]

リフレクションでも、通常は プライベートなメソッドを呼ぶことは出来ないし、プライベートなフィールドの値を読み書きすることは出来ない。実行しようとするとjava.lang.IllegalAccessExceptionが発生する。
しかし属性を“アクセス可能”に変えてやると、アクセスできるようになる。

	Target target = new Target();
	Class<Target> c = Target.class;

	Method m = c.getDeclaredMethod("setValue", int.class);
	m.setAccessible(true);
	m.invoke(target, 123);			// target.setValue(123);
	Field f = c.getDeclaredField("value");
	f.setAccessible(true);
	int n = f.getInt(target);			// int n = target.value;

ただし、セキュリティーマネージャーによってアクセス制限がかけられている場合は、属性を変更することは出来ない。
setAccessible()呼び出し時にjava.security.AccessControlException(access denied/suppressAccessChecks)が発生する。
(Javaアプリケーションのデフォルトではアクセス制限がかけられていないので、JUnitでprivateメソッドをテストしたいとき等にプライベートアクセスを使うことが出来る)

ちなみにJNIなら、こういったアクセス制御の変更をせずにプライベートなメンバーにアクセスできる。[2008-02-10]


finalフィールドへ値の設定

リフレクションでは、final付きのフィールドへの値の設定も(条件付きで)行うことが出来る。[2010-01-15/2013-08-06]

public class Target {
	public final int VALUE = 999;
}
Field f = clazz.getDeclaredField("VALUE");
f.setInt(target, 123);
System.out.println(target.VALUE);
値をセットするメソッドでjava.lang.IllegalAccessExceptionが発生する。
Field f = clazz.getDeclaredField("VALUE");
f.setAccessible(true);
f.setInt(target, 123);
System.out.println(target.VALUE); //変わっていない
System.out.println(f.getInt(target)); //変わっている
属性を“アクセス可能”に変えてやると、例外は発生せず正常に終了する。
ただし最適化(インライン展開)されている箇所では値は変わらない。[/2013-08-06]

クラスのメンバー確認方法

クラス内のメソッドやフィールド変数は、javapというコマンドで確認できる。[2007-02-09]
JNIC言語側からJavaのメソッドを呼びたい時にけっこう重宝する。

使用方法: javap クラス名

>cd C:\workspace\sample\classes
>javap jp.hishidama.sample.Sample

Java目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま