Javaのリフレクションはクラス、インタフェース、メソッドを見つけることができ、オブジェクトを作成したり変数を変更したり、メソッドを呼び出すことができます。 ReflectionはJavaがデフォルトで提供するAPIです。使い方だけを知ればライブラリを追加する必要なく使用できます。
AndroidでHiddenメソッドを呼び出すときにReflectionを使用できます。 SDKにAPIが公開されていない場合、Android Studioでは参照できず呼び出すことはできませんが、実際にhidden APIが存在するため、リフレクションを使用して呼び出すことができます。
あるいは、テストコードを作成するためにプライベート変数を変更するときにリフレクションを使用することもできます。 3rd partyライブラリを使用し、そのプライベート変数を変更したい場合は、リフレクションを使用してライブラリコードを変更せずに値を変更できます。
Reflection は次の情報を取得できます。この情報を取得してオブジェクトを作成したり、メソッドを呼び出したり、変数の値を変更したりできます。
- Class
- Constructor
- Method
- Field
Reflectionは使用するたびに混乱するAPIですが、何度か練習してみるとReflectionに慣れることができます。 まず、Reflectionでクラスなどの情報を取得する方法を学び、次にメソッドの呼び出しと変数を変更する方法を学びます。
準備
チュートリアルに従う前に、リフレクションを適用するクラスを事前に作成する必要があります。 Javaライブラリにリフレクションを適用できますが、作成したクラスを見るとわかりやすいので、2つのクラスを作成しました。 次のようにChildとParentクラスを作成しました。 Child は Parent クラスを継承します。
Parent.java
package test;
public class Parent {
private String str1 = "1";
public String str2 = "2";
public Parent() {
}
private void method1() {
System.out.println("method1");
}
public void method2(int n) {
System.out.println("method2: " + n);
}
private void method3() {
System.out.println("method3");
}
}
Child.java
package test;
public class Child extends Parent {
public String cstr1 = "1";
private String cstr2 = "2";
public Child() {
}
private Child(String str) {
cstr1 = str;
}
public int method4(int n) {
System.out.println("method4: " + n);
return n;
}
private int method5(int n) {
System.out.println("method5: " + n);
return n;
}
}
Test.java
Testクラスのmainでreflectionを使用します。次のように、次の3つのクラスをインポートする必要があります。
- java.lang.reflect.Method
- java.lang.reflect.Field
- java.lang.reflect.Constructor
package test;
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
class Test {
public static void main(String args[]) throws Exception {
}
}
Classを探す
クラス Class
オブジェクトはクラスまたはインタフェースを指します。 java.lang.Class
で、importせずに使用できます。
次のコードを見ると、 Child.class
のようにクラス情報を割り当てることができます。クラスオブジェクトはいくつかのメソッドを提供し、 getName()
はクラスの名前を返します。
コード実行の結果はコメントで書かれています。
Class clazz = Child.class;
System.out.println("Class name: " + clazz.getName());
Output:
Class name: test.Child
上記の例は、IDEでクラスを知っているという前提に使用できました。クラスを参照できず、名前だけを知っている場合は、どのようにクラス情報を取得するのですか?
次のコードは、クラス名だけでクラス情報を取得します。 Class.forName()
にクラス名を引数として渡すことで、クラス情報を取得できます。
パッケージ名を含むクラス名で書く必要があります。
Class clazz2 = Class.forName("test.Child");
System.out.println("Class name: " + clazz2.getName());
Output:
Class name: test.Child
Constructorを探す
クラスを見つけたら、コンストラクタを探してみましょう。
次のコードは、クラスからコンストラクタを取得するコードです。 getDeclaredConstructor()
は引数のないコンストラクタを取得します。
Class clazz = Class.forName("test.Child");
Constructor constructor = clazz.getDeclaredConstructor();
System.out.println("Constructor: " + constructor.getName());
Output:
Constructor: test.Child
getDeclaredConstructor(Param)
に引数を入れると、その型と一致するコンストラクタを探します。
Class clazz = Class.forName("test.Child");
Constructor constructor2 = clazz.getDeclaredConstructor(String.class);
System.out.println("Constructor(String): " + constructor2.getName());
Output:
Constructor(String): test.Child
上記の2つの例は、引数を受け取るコンストラクタだけを見つけるコードです。
次のコードはすべてのコンストラクタを取得します。 getDeclaredConstructors()
はクラスの private、public などのすべてのコンストラクタを返します。
Class clazz = Class.forName("test.Child");
Constructor constructors[] = clazz.getDeclaredConstructors();
for (Constructor cons : constructors) {
System.out.println("Get constructors in Child: " + cons);
}
Output:
Get constructors in Child: private test.Child(java.lang.String)
Get constructors in Child: public test.Child()
次のコードはpublicコンストラクタだけを返します。
Class clazz = Class.forName("test.Child");
Constructor constructors2[] = clazz.getConstructors();
for (Constructor cons : constructors2) {
System.out.println("Get public constructors in Child: " + cons);
}
Output:
Get public constructors in both Parent and Child: public test.Child()
Methodを探す
次に、クラスでメソッドを探しましょう。
次のコードは、名前でメソッドを検索するコードです。 getDeclaredMethod()
の引数でメソッドのパラメータ情報を渡すと一致するものを見つけます。
Class clazz = Class.forName("test.Child");
Method method1 = clazz.getDeclaredMethod("method4", int.class);
System.out.println("Find out method4 method in Child: " + method1);
Output:
Find out method4 method in Child: public int test.Child.method4(int)
引数がないメソッドなら、次のようにnullを渡すだけです。 getDeclaredMethod()
でメソッドを探すときに存在しない場合、NoSuchMethodExceptionエラーが発生します。
Class clazz = Class.forName("test.Child");
Method method1 = clazz.getDeclaredMethod("method4", null);
引数が2つ以上の場合は、次のようにクラス配列を作成して引数を入れます。
Class clazz = Class.forName("test.Child");
Class partypes[] = new Class[1];
partypes[0] = int.class;
Method method = clazz.getDeclaredMethod("method4", partypes);
すべてのメソッドを見つけるには、次のように getDeclaredMethods
を使用します。
一般的に関数名に Declared
が入っていると、Super クラスの情報は取得されません。
Class clazz = Class.forName("test.Child");
Method methods[] = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println("Get methods in Child: " + method);
}
Output:
Get methods in Child: public int test.Child.method4(int)
Get methods in Child: private int test.Child.method5(int)
getMethods()
は public メソッドを返し、継承されたメソッドもすべて探します。
Class clazz = Class.forName("test.Child");
Method methods2[] = clazz.getMethods();
for (Method method : methods2) {
System.out.println("Get public methods in both Parent and Child: " + method);
}
Output:
Get public methods in both Parent and Child: public int test.Child.method4(int)
Get public methods in both Parent and Child: public void test.Parent.method2(int)
Get public methods in both Parent and Child: public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
Get public methods in both Parent and Child: public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
Get public methods in both Parent and Child: public final void java.lang.Object.wait() throws java.lang.InterruptedException
Get public methods in both Parent and Child: public boolean java.lang.Object.equals(java.lang.Object)
Get public methods in both Parent and Child: public java.lang.String java.lang.Object.toString()
Get public methods in both Parent and Child: public native int java.lang.Object.hashCode()
Get public methods in both Parent and Child: public final native java.lang.Class java.lang.Object.getClass()
Get public methods in both Parent and Child: public final native void java.lang.Object.notify()
Get public methods in both Parent and Child: public final native void java.lang.Object.notifyAll()
Field(変数)の変更
ここでは、クラスでField(変数)情報を探します。
コンストラクタとメソッドの例に似ています。 getDeclaredField()
に渡された名前と一致する Field を探します。
Class clazz = Class.forName("test.Child");
Field field = clazz.getDeclaredField("cstr1");
System.out.println("Find out cstr1 field in Child: " + field);
Output:
Find out cstr1 field in Child: public java.lang.String test.Child.cstr1
オブジェクトで宣言されているすべてのフィールドを見つけるには、 getDeclaredFields()
を使うだけです。上記のように、継承されたオブジェクトの情報は見つかりません。
Class clazz = Class.forName("test.Child");
Field fields[] = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println("Get fields in Child: " + field);
}
Output:
Get fields in Child: public java.lang.String test.Child.cstr1
Get fields in Child: private java.lang.String test.Child.cstr2
継承したクラスを含むパブリックフィールドを見つけるには、 getFields()
を使うだけです。
Class clazz = Class.forName("test.Child");
Field fields2[] = clazz.getFields();
for (Field field : fields2) {
System.out.println("Get public fields in both Parent and Child: " + field);
}
Output:
Get public fields in both Parent and Child: public java.lang.String test.Child.cstr1
Get public fields in both Parent and Child: public java.lang.String test.Parent.str2
Method呼び出し
クラスからメソッド情報を取得し、オブジェクトのメソッドを呼び出すことができます。
メソッドオブジェクトを作成したら、 Method.invoke()
で呼び出すことができます。最初の引数は呼び出すオブジェクトで、2番目の引数は渡すパラメータ値です。
メソッドが何らかの値を返すと、その値を受け取ることができます。
Child child = new Child();
Class clazz = Class.forName("test.Child");
Method method = clazz.getDeclaredMethod("method4", int.class);
int returnValue = (int) method.invoke(child, 10);
System.out.println("return value: " + returnValue);
Output:
method4: 10
return value: 10
次のコードは、Parentのmethod1()を呼び出す例です。このメソッドは引数がないので、 getDeclaredMethod()
に引数を入力する必要はありません。
そして getDeclaredMethod
は継承されたクラスの情報を取得しないので、 Parent
のクラス情報を取得しなければなりません。
Child child = new Child();
Class clazz = Class.forName("test.Parent");
Method method = clazz.getDeclaredMethod("method1");
method.invoke(child);
上記のコードを実行すると、次のエラーが発生します。理由は、method1() がプライベートであるためです。
Exception in thread "main" java.lang.IllegalAccessException: Class test.Test can not access a member of class test.Parent with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
at java.lang.reflect.Method.invoke(Method.java:491)
at test.Test.main(Test.java:93)
次のように setAccessible(true)
に設定すると、private メソッドにアクセスできるように変更されます。
実行してみると呼び出されます。
Child child = new Child();
Class clazz = Class.forName("test.Parent");
Method method = clazz.getDeclaredMethod("method1");
method.setAccessible(true);
method.invoke(child);
Output:
method1
Field 変更
クラスから変数情報を取得し、オブジェクトの変数を変更できます。
次のコードは、 cstr1
変数を取得して値を出力して変更し、再度出力する例です。
Child child = new Child();
Class clazz = Class.forName("test.Child");
Field fld = clazz.getField("cstr1");
System.out.println("child.cstr1: " + fld.get(child));
fld.set(child, "cstr1");
System.out.println("child.cstr1: " + fld.get(child));
Output:
child.cstr1: 1
child.cstr1: cstr1
プライベート変数を変更するには、上記のように setAccessible(true)
でアクセス状態を変更するだけです。
Child child = new Child();
Class clazz = Class.forName("test.Child");
Field fld2 = clazz.getDeclaredField("cstr2");
fld2.setAccessible(true);
fld2.set(child, "cstr2");
System.out.println("child.cstr2: " + fld2.get(child));
Output:
child.cstr2: cstr2
Static メソッドの呼び出しまたはフィールドの変更
これまで、一般的なクラスとオブジェクトに対してリフレクションを使用する方法を学びました。 Staticメソッド、フィールドに頻繁に接することができます。これらのstaticメソッド、フィールドはどのようにリフレクションでアクセスできるかを見てみましょう。
StaticExample.java
チュートリアルに従うには、Staticクラスを追加する必要があります。
package test;
public class StaticExample {
public static String EXAMPLE = "Example";
public static int getSquare(int num) {
System.out.println("Get square: " + num * num);
return num * num;
}
}
Staticメソッドの情報を取得して呼び出してみましょう。
メソッド情報の取得方法は上記と同じです。ただし、呼び出すときに invoke()
でオブジェクトを渡す引数に null
を入れてください。その後、 static メソッドが呼び出されます。
Class clazz = Class.forName("test.StaticExample");
Method method = clazz.getDeclaredMethod("getSquare", int.class);
method.invoke(null, 10);
Output:
Get square: 100
static フィールド情報を取得する方法も上記と同じです。代わりに、 set()
または get()
関数を使用するとき、オブジェクトに渡される引数に null
を入れなければなりません。
Class clazz = Class.forName("test.StaticExample");
Field fld = clazz.getDeclaredField("EXAMPLE");
fld.set(null, "Hello, World");
System.out.println("StaticExample.EXAMPLE: " + fld.get(null));
Output:
StaticExample.EXAMPLE: Hello, World
クリーンアップ
私はメソッド呼び出しや変数を変更したい目的でリフレクションを使います。 リフレクションで引数の型などの他の情報を取得したい場合は、oracleなどのDocumentや他の例を参照してください。
References
Related Posts
- Java - AtomicReference使用方法
- Java - CountDownLatchを使用する方法、および例
- Java - ScheduledThreadPoolExecutor使用方法
- Java - SummaryStatisticsの使用方法(count、min、max、average)
- Java8 - 関数型インタフェース (Functional Interface) について
- Java - String配列をint配列に変換する
- Java - ArrayList要素の値を変更する方法、replaceAll()
- Java - 2つのリストが同じかどうかを比較
- Java - 配列から特定のIndex要素を削除する3つの方法
- Java - HashMapソート、4つの方法
- Java - 文字列を配列に変換する方法
- Java - ArrayListが空であることを確認する3つの方法
- Java - ArrayListの巡回、4つの方法
- Java - ArrayListの最大値、最小 値を見つける
- Java - ArrayListの合計、平均値の計算
- Java - HashMap巡回、3つの方法
- Java - do whileとwhileの違い
- Java - Lambda式と関数型インタフェース
- Java - List empty(null)チェック、3つの方法
- Java - ArrayListの初期化、4つの方法
- Java - Stream.reduce()の使い方と例
- Java - 2つのマップを結合する(merge、putAll)
- Java - java.util.Dateをjava.sql.Dateに変換する
- Java - ArrayListをStringに変換する
- Java - ClassNotFoundExceptionの発生原因と解決策
- Java - 非静的メソッドは静的コンテキストから参照できません
- Java - NoSuchMethodErrorの原因と解決策
- Java - JSONライブラリを使用する方法(JSONObject、JSONArray)
- Java - byte[]配列をFileに保存
- Java - byte[]の配列をStringに変換
- Java - ファイルのアクセス権を確認し、変更
- Java - 一時フォルダ(Temp directory)パスを取得する
- Javaでシェルスクリプトを実行
- Java - Streamを配列に変換する
- Java - リスト重複排除、2つの方法