Movatterモバイル変換


[0]ホーム

URL:


S-JIS[2004-06-12/2021-03-21]変更履歴

リフレクション

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; //配列もOKClass 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<? extendsVoid> 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<? extendsString> 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()Packageパッケージを返す。 
getPackageName()Stringパッケージ名を返す。Java9以降2020-09-29
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()AnnotatedTypeJDK1.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
getRecordComponents()RecordComponent[]レコードコンポーネントの一覧を返す。2021-03-21
getDeclaredMethods()Method[]自分が直接宣言している全てのメソッドを返す。 
getMethods()Method[]自分が扱えるpublicなメソッドを返す。(スーパークラスの分も含む)2011-09-22
getMethod("メソッド名", 引数の型の配列)
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
componentType()Class<?>配列の場合、要素の型(クラス)を返す。Java12以降
(配列でない場合はnullが返る)
2020-09-29
arrayType()Class<?>自分を要素とする配列の型(クラス)を返す。Java12以降2020-09-29
isHidden()booleanhiddenクラスかどうかを返す。Java15以降
→nowokayさんのJava 15新機能まとめ
→ccoさんのJava 14とJava 15の新機能解説
2020-09-29
equals(obj)booleanClass#equals()は特にオーバーライドされていないので、
Object#equals()そのもの。
すなわち「this==obj」なので、equals()を使わずに
直接「==演算子」を用いて比較してよい。
2010-01-25
toGenericString()StringtoString()に修飾情報(可視性等)も加えた文字列を返す。JDK1.8以降2014-03-19
descriptorString()String型記述子文字列を返す。Java12以降
(Stringだと「Ljava/lang/String;」が返る)
2020-09-29

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

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

Classからのインスタンス生成

リフレクションを使って引数無しコンストラクターからインスタンスを生成する為にClass#newInstance()というメソッドがある。(ただし、JDK1.9で非推奨となった[2018-04-30]

JDK1.4以前
Classclazz = 〜;
Object obj =clazz.newInstance();//     obj = newクラス(); と同じ
Classclazz = 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]

引数無しのClass#newInstance()がJDK1.9で非推奨になったのは、コンストラクターで発生する例外を正しく処理できない為らしい。[2018-04-30]
JDK1.9以前でも問題は発生しうる。

コンストラクターでthrows宣言されている(RuntimeException以外の)例外がある場合、Class#newInstance()はその例外のcatchを書くことが出来ない。

public ClassExample {// 例外が発生するコンストラクターpublic ClassExample() throws IOException {throw new IOException("example");}}
try {ClassExample.class.newInstance();} catch (InstantiationException| IllegalAccessException e) {e.printStackTrace();} catch(IOException e) { // このcatchを書くことは出来ないが、IOExceptionは発生するe.printStackTrace();}

コンストラクターを取得する方法なら大丈夫。(Constructor#newInstance()では、コンストラクターで発生した例外はInvocationTargetExceptionでラップされる)


Constructorを使ったインスタンス生成

リフレクションでインスタンスを生成するには、一旦コンストラクターを取得し、そのnewInstance()を使う。
(→配列のインスタンス生成はArrayクラスのnewInstance()を使う

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

 
import java.lang.reflect.Constructor;
 
JDK1.4以前
Classclazz = 〜;
Class[] types =引数の型の配列;Constructorconstructor =clazz.getConstructor(types);Object[] args =引数の値の配列;Object obj =constructor.newInstance(args);//     obj = newクラス(引数,…); と同じ
Classclazz = Integer.class;Class[] types = {int.class };Constructorconstructor;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]
可変長引数なので、もちろん今まで通りの配列の方法も使える。

// コンストラクターの引数が無い場合[2018-04-30]Constructor<?> ct = clazz.getConstructor();Object obj = ct.newInstance();
// コンストラクターの引数がint2つの場合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 ArrayListnewArrayList(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 ListnewList(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.newA()」だが、いわば「newA(x)」とでも言うべき暗黙の引数が存在しているようだ。
そういうコンストラクターが存在している事は、クラスのコンパイル後の定義を見てみると分かる。(逆に「引数なしコンストラクター」は存在していない…ソース上は引数無しであっても!)

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

JDK1.4以前
Xx = newX();Class cl =X.A.class;Constructor con = cl.getConstructor(new Class[]{X.class });X.Aa = (X.A)con.newInstance(new Object[]{x });
JDK1.5以降
Xx = newX();Class<X.A> cl =X.A.class;Constructor<X.A> con = cl.getConstructor(X.class);X.Aa = con.newInstance(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メソッド実行

JDK1.4以前
Methodmethod;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 e;} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);}
呼び出すメソッドに引数が無い場合は、
getMethodおよびinvokeの第2引数(配列)にnull(または空の配列)を指定する。

staticメソッドを呼び出す場合、invokeの第1引数にはnullを指定する。
JDK1.5以降
Methodmethod;try {method=clazz.getMethod("メソッド名");} catch (SecurityException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);}Object ret; //戻り値try {ret =method.invoke(null);//  =クラス名.メソッド名(); と同じ} catch (IllegalArgumentException e) {throw e;} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);}
可変長引数を利用すると、呼び出すメソッドに引数が無い場合は、
getMethodおよびinvokeの第2引数以降を指定する必要が無い。[2017-07-23]
JDK1.7以降
Methodmethod;try {method=clazz.getMethod("メソッド名");} catch (ReflectiveOperationException e) {throw new RuntimeException(e);}Object ret; //戻り値try {ret =method.invoke(null);//  =クラス名.メソッド名(); と同じ} catch (IllegalArgumentException e) {throw e;} catch (ReflectiveOperationException e) {throw new RuntimeException(e);}
JDK1.7で、リフレクション系の例外の共通クラスが定義された。[2017-07-23]

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

JDK1.4以前
Methodmethod;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 e;} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);}
呼び出すメソッドに引数が無い場合は、
getMethodおよびinvokeの第2引数(配列)にnull(または空の配列)を指定する。

インスタンスメソッドを呼び出す場合、invokeの第1引数には対象インスタンスを指定する。
JDK1.5以降
Methodmethod;try {method=object.getClass().getMethod("メソッド名");} catch (SecurityException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);}Object ret; //戻り値try {ret =method.invoke(object);//  =object.メソッド名(); と同じ} catch (IllegalArgumentException e) {throw e;} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);}
可変長引数を利用すると、呼び出すメソッドに引数が無い場合は、
getMethodおよびinvokeの第2引数以降を指定する必要が無い。[2017-07-23]
JDK1.7以降
Methodmethod;try {method=object.getClass().getMethod("メソッド名");} catch (ReflectiveOperationException e) {throw new RuntimeException(e);}Object ret; //戻り値try {ret =method.invoke(object);//  =object.メソッド名(); と同じ} catch (IllegalArgumentException e) {throw e;} catch (ReflectiveOperationException e) {throw new RuntimeException(e);}
JDK1.7で、リフレクション系の例外の共通クラスが定義された。[2017-07-23]

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

JDK1.4以前
Methodmethod;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 e;} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);}
呼び出すメソッドに引数がある場合は、
getMethodの第2引数には引数の型の配列
invokeの第2引数にはの配列
を指定する。

引数の型がint等のプリミティブ型の場合、invokeに指定するにはInteger等のラッパークラスを使用する。
JDK1.5以降
Methodmethod;try {method=object.getClass().getMethod("メソッド名",int.class);} catch (SecurityException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);}Object ret; //戻り値try {ret =method.invoke(object,1);//  =object.メソッド(1); と同じ} catch (IllegalArgumentException e) {throw e;} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);}
呼び出すメソッドに引数がある場合は、可変長引数を利用して
getMethodの第2引数以降に引数の型
invokeの第2引数以降に
を並べるだけで良い。[2017-07-23]

引数の型がint等のプリミティブ型の場合、invokeに指定するにはintの値をそのまま使用できる。
オートボクシングにより、自動的にInteger等のラッパークラスに変換される)

JDK1.7以降
Methodmethod;try {method=object.getClass().getMethod("メソッド名",int.class);} catch (ReflectiveOperationException e) {throw new RuntimeException(e);}Object ret; //戻り値try {ret =method.invoke(object,1);//  =object.メソッド(1); と同じ} catch (IllegalArgumentException e) {throw e;} catch (ReflectiveOperationException e) {throw new RuntimeException(e);}
JDK1.7で、リフレクション系の例外の共通クラスが定義された。[2017-07-23]

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

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


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

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

ただし、「引数なし」を意味する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.8int引数の個数を返す。
getParameterTypes()Class<?>[]引数の型(引数の個数分の配列)を返す。
getParameterAnnotations()1.5Annotation[][]引数に付けられたアノテーション(引数の個数分の配列)を返す。
getParameters()1.8Parameter[]引数の一覧を返す。

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(クラス)AgetAnnotation()と同じ。
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 classJavaBeanSample {private String str;public voidsetData(String s) {this.str = s;}publicStringgetData() {return str;}}

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

import java.beans.PropertyDescriptor;
JavaBeanSamplebean = newJavaBeanSample();PropertyDescriptorpd = newPropertyDescriptor("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));
判断内容判断方法ClassAccessible
Object
MemberConst
ructor
MethodField更新日
プリミティブ型かどうか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
レコードかどうかisRecord()16     2021-03-21
アノテーションかどうか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)) == 02008-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
hiddenクラスかどうかisHidden()15     2021-03-21

メソッドの戻り型

メソッドの戻り値の型は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目次へ戻る /新機能へ戻る /技術メモへ戻る
メールの送信先:ひしだま

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


[8]ページ先頭

©2009-2025 Movatter.jp