Java - Reflection 簡単かつ迅速に理解する

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

codechachaCopyright ©2019 codechacha