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<
|
//このような書き方も可 Class<? extends クラス名> clazz = クラス名.class; |
Class<? extends String> strClass = String.class;
Class<? extends Integer> intClass = int.class;
Class<? extends String[]> arrClass = String[].class;
Class<? extends
|
また、どんなインスタンスでも、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() |
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() |
AnnotatedType | JDK1.8以降 | 2014-03-19 |
getInterfaces() |
Class[] | 自分が直接実装しているインターフェースを返す。 (スーパークラスで実装しているインターフェースは対象外) |
|
getAnnotatedInterfaces() |
AnnotatedType[] | JDK1.8以降 | 2014-03-19 |
getConstructors() |
Constructor[] | publicなコンストラクターを返す。 | |
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 ("メソッド名", 引数の型の配列) |
Method | メソッドを返す。(引数の種類が合致するpublicなもの) | |
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() |
boolean | hiddenクラスかどうかを返す。Java15以降 →nowokayさんのJava 15新機能まとめ →ccoさんのJava 14とJava 15の新機能解説 |
2020-09-29 |
equals(obj) |
boolean | Class#equals()は特にオーバーライドされていないので、 Object#equals()そのもの。 すなわち「this==obj」なので、equals()を使わずに 直接「==演算子」を用いて比較してよい。 |
2010-01-25 |
toGenericString() |
String | toString()に修飾情報(可視性等)も加えた文字列を返す。JDK1.8以降 | 2014-03-19 |
descriptorString() |
String | 型記述子文字列を返す。Java12以降 (Stringだと「Ljava/lang/String;」が返る) |
2020-09-29 |
あるオブジェクトがあるクラスのサブクラスであるかどうか(継承しているかどうか)は、以下のようにして判定する。[2007-02-16]
if (obj instanceof クラス) 〜; if (クラス.class.isInstance(obj)) 〜; if (クラス.class.isAssignableFrom(obj.getClass()) 〜;
リフレクションを使って引数無しコンストラクターからインスタンスを生成する為にClass#newInstance()というメソッドがある。(ただし、JDK1.9で非推奨となった[2018-04-30])
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] |
引数無しの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でラップされる)
リフレクションでインスタンスを生成するには、一旦コンストラクターを取得し、そのnewInstance()を使う。
(→配列のインスタンス生成はArrayクラスの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]
可変長引数なので、もちろん今まで通りの配列の方法も使える。
// コンストラクターの引数が無い場合 [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]
ClassやConstructorはどのクラスも扱うわけだが、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)
」とでも言うべき暗黙の引数が存在しているようだ。
そういうコンストラクターが存在している事は、クラスのコンパイル後の定義を見てみると分かる。
(逆に「引数なしコンストラクター」は存在していない…ソース上は引数無しであっても!)
という訳で、内部クラスをリフレクションを使ってインスタンス化するには、引数ありコンストラクターと同じ方法を用いる。
JDK1.4以前 |
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 }); |
JDK1.5以降 |
X x = new X(); Class<X.A> cl = X.A.class; Constructor<X.A> con = cl.getConstructor(X.class); X.A a = 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()で調べる)
JDK1.4以前 |
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 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以降 |
Method method; 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以降 |
Method method; 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以前 |
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 e; } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } |
呼び出すメソッドに引数が無い場合は、 getMethodおよびinvokeの第2引数(配列)にnull(または空の配列)を指定する。 インスタンスメソッドを呼び出す場合、invokeの第1引数には対象インスタンスを指定する。 |
JDK1.5以降 |
Method method; 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以降 |
Method method; 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以前 |
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 e; } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } |
呼び出すメソッドに引数がある場合は、 getMethodの第2引数には引数の型の配列 invokeの第2引数には値の配列 を指定する。 引数の型がint等のプリミティブ型の場合、invokeに指定する値にはInteger等のラッパークラスを使用する。 |
JDK1.5以降 |
Method method; 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の値をそのまま使用できる。 |
JDK1.7以降 |
Method method; 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()の引数に(配列を使わずに)実引数を並べるような書き方は出来ない。
要するに普通に配列を扱うのと同様にコーディングする。
可変長引数以外の引数が無い | 可変長引数以外の引数が有る | |
---|---|---|
取得したいメソッド | 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[] args = { "123", new String[]{ "abc", "def" } }; |
呼び出し方法 (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クラスからコンストラクターの引数の情報を取得できる)
メソッド名 | 戻り型 | 説明 | |
---|---|---|---|
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;
メソッド名 | 戻り型 | 説明 |
---|---|---|
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フィールドへのアクセスは、メソッドと同じく、対象オブジェクトに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));
リフレクションの範疇に入るかどうかは怪しいが、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の動作が変わったのは、これが関係していたのかなぁ…?
クラスやメンバー(メソッド・フィールド)の属性(修飾子)は、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 | |||||
レコードかどうか | 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)) == 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 | |||||
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なメソッドやフィールドも(条件付きで)アクセスすることが出来る。[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付きのフィールドへの値の設定も(条件付きで)行うことが出来る。[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]
JNIでC言語側からJavaのメソッドを呼びたい時にけっこう重宝する。
使用方法: javap クラス名
>cd C:\workspace\sample\classes >javap jp.hishidama.sample.Sample