JavaのリフレクションAPIでメソッド取得、メソッド実行

JavaのリフレクションAPIでメソッドの取得、メソッドの実行を試してみたメモです。

JavaのリフレクションってforName("ClassName")くらいしか使ったことなかったので、
これを機にもう少しちゃんと勉強してみようと思いました。

java.lang.reflect.Method

JavaのリフレクションAPIを利用することで、メタデータにアクセスできます。
java.lang.reflect.Methodインターフェースを利用してメソッドの取得、メソッドの実行を試してみたいと思います。

テスト用クラス

Mavenプロジェクトを作成し、pom.xmljunitとhamcrestだけdependencyに追加しました。

pom.xml

    <dependencies>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-all</artifactId>
            <version>1.3</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit-dep</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

テスト対象のクラスは下記のようなクラスを作りました。

package reflect;

public class Circle {
    private int radius;

    public Circle() {
        this(10);
    }

    public Circle(int radius) {
        this.radius = radius;
    }

    public int area() {
        return area(radius);
    }

    public int area(int r) {
        return (int) (r * r * Math.PI);
    }

    public int publicMethod() {
        return 1;
    }

    protected int protectedMethod() {
        return 2;
    }

    int defaultMethod() {
        return 3;
    }

    private int privateMethod() {
        return 4;
    }

    public static int staticMethod() {
        return 11;
    }

    @Override
    public String toString() {
        return "radius : " + radius;
    }
}

public、protected、修飾子なし、private
のアクセス修飾子でそれぞれメソッドを作成しました。

あと、toString()のみオーバーライドしています。

Methodオブジェクト

Methodオブジェクトを取得するためのメソッドは下記です。
すべてClassクラスのメソッドです。

メソッド 戻り値 取得可能なアクセス修飾子 取得可能なクラス
getMethod Method public 自クラスとスーパークラス
getMethods Method[] public 自クラスとスーパークラス
getDeclaredMethod Method すべて 自クラス
getDeclaredMethods Method[] すべて 自クラス
getMethod()

getMethod()では、自クラスとスーパークラスのメソッドが取得できます。
取得できるのはpublicメソッドのみです。
引数にはメソッド名の文字列を指定します。

    Method method = Circle.class.getMethod("area");

同じメソッドでオーバーロードしている場合は、第2引数以降に引数の型を指定します。

    // 引数なし
    Method method = Circle.class.getMethod("area");

    // 引数あり
    Method method = Circle.class.getMethod("area", int.class);

スーパークラスのメソッドも取得できるので、Circleクラスでは定義していないhashcode()が取得できます。

    // Objectクラスで定義されたメソッド
    Method method = Circle.class.getMethod("hashCode");

テストコードはこんな感じで書きました。

    public static class getMethodの確認 {
        @Test
        public void getMethodでareaメソッドのMethodオブジェクトが取得できること() throws Exception {
            Method method = Circle.class.getMethod("area");
            assertThat(method.toString(), is("public int reflect.Circle.area()"));
        }

        @Test
        public void getMethodで引数ありのareaメソッドのMethodオブジェクトが取得できること() throws Exception {
            Method method = Circle.class.getMethod("area", int.class);
            assertThat(method.toString(), is("public int reflect.Circle.area(int)"));
        }

        @Test
        public void getMethodでpublicメソッドが取得できること() throws Exception {
            Method pub = Circle.class.getMethod("publicMethod");
            assertThat(pub.toString(), is("public int reflect.Circle.publicMethod()"));
        }

        @Test(expected = NoSuchMethodException.class)
        public void getMethodでprotectedメソッドが取得できないこと() throws Exception {
            Method pro = Circle.class.getMethod("protectedMethod");
            assertThat(pro.toString(), is("protected int reflect.Circle.protectedMethod()"));
        }

        @Test(expected = NoSuchMethodException.class)
        public void getMethodでdefaultメソッドが取得できないこと() throws Exception {
            Method def = Circle.class.getMethod("defaultMethod");
            assertThat(def.toString(), is("int reflect.Circle.defaultMethod()"));
        }

        @Test(expected = NoSuchMethodException.class)
        public void getMethodでprivateメソッドが取得できないこと() throws Exception {
            Method pri = Circle.class.getMethod("privateMethod");
            assertThat(pri.toString(), is("private int reflect.Circle.privateMethod()"));
        }

        @Test
        public void getMethodでhashCodeメソッドが取得できること() throws Exception {
            Method method = Circle.class.getMethod("hashCode");
            assertThat(method.toString(), is("public native int java.lang.Object.hashCode()"));
        }
    }
getMethods()

getMethods()では、自クラスとスーパークラスのメソッドがMethodクラスの配列で取得できます。
取得できるのはpublicメソッドのみです。

スーパークラスのメソッドも取得できるので、Objectクラスで定義されているpublicメソッドも取得されます。

テストコードはこんな感じで書きました。

    public static class getMethodsの確認 {
        @Test
        public void getMethodsでCircleクラスとスーパークラスのメソッドが取得できること() throws Exception {
            Class clazz = Circle.class;
            Method[] methods = clazz.getMethods();
            Method[] expected = {
                    clazz.getMethod("area"),
                    clazz.getMethod("area", int.class),
                    clazz.getMethod("publicMethod"),
                    clazz.getMethod("staticMethod"),
                    clazz.getMethod("toString"),
                    clazz.getMethod("wait"),
                    clazz.getMethod("wait", long.class),
                    clazz.getMethod("wait", long.class, int.class),
                    clazz.getMethod("equals", Object.class),
                    clazz.getMethod("hashCode"),
                    clazz.getMethod("getClass"),
                    clazz.getMethod("notify"),
                    clazz.getMethod("notifyAll")
            };
            assertThat(methods, is(arrayContainingInAnyOrder(expected)));
        }
    }
getDeclaredMethod()

getDeclaredMethod()では、自クラスで定義されたメソッドのみ取得できます。
getMethod()と異なり、すべてのアクセス修飾子(public、protected、デフォルト、private)のメソッドが取得できます。

    // publicメソッド
    Method pub = Circle.class.getDeclaredMethod("publicMethod");

    // protectedメソッド
    Method pro = Circle.class.getDeclaredMethod("protectedMethod");

    // defaultメソッド
    Method def = Circle.class.getDeclaredMethod("defaultMethod");

    // privateメソッド
    Method pri = Circle.class.getDeclaredMethod("privateMethod");

スーパークラスのメソッドは取得できませんが、オーバーライドしていれば取得可能です。

    // toString()はオーバーライドしているため取得可能
    Method toStr = Circle.class.getDeclaredMethod("toString");

テストコードはこんな感じで書きました。

    public static class getDeclaredMethodの確認 {
        @Test
        public void getDeclaredMethodでareaメソッドが取得できること() throws Exception {
            Method method = Circle.class.getDeclaredMethod("area");
            assertThat(method.toString(), is("public int reflect.Circle.area()"));
        }

        @Test
        public void getDeclaredMethodでpublicメソッドが取得できること() throws Exception {
            Method pub = Circle.class.getDeclaredMethod("publicMethod");
            assertThat(pub.toString(), is("public int reflect.Circle.publicMethod()"));
        }

        @Test
        public void getDeclaredMethodでprotectedメソッドが取得できること() throws Exception {
            Method pro = Circle.class.getDeclaredMethod("protectedMethod");
            assertThat(pro.toString(), is("protected int reflect.Circle.protectedMethod()"));
        }

        @Test
        public void getDeclaredMethodでdefaultメソッドが取得できること() throws Exception {
            Method def = Circle.class.getDeclaredMethod("defaultMethod");
            assertThat(def.toString(), is("int reflect.Circle.defaultMethod()"));
        }

        @Test
        public void getDeclaredMethodでprivateメソッドが取得できること() throws Exception {
            Method pri = Circle.class.getDeclaredMethod("privateMethod");
            assertThat(pri.toString(), is("private int reflect.Circle.privateMethod()"));
        }

        @Test
        public void getDeclaredMethodでオーバーライドメソッドが取得できること() throws Exception {
            Method toStr = Circle.class.getDeclaredMethod("toString");
            assertThat(toStr.toString(), is("public java.lang.String reflect.Circle.toString()"));
        }

        @Test(expected = NoSuchMethodException.class)
        public void getDeclaredMethodでhashCodeメソッドが取得できないこと() throws Exception {
            Method method = Circle.class.getDeclaredMethod("hashCode");
            assertThat(method.toString(), is("public native int java.lang.Object.hashCode()"));
        }
    }
getDeclaredMethods()

getDeclaredMethods()では、自クラスで定義されたメソッドがMethodクラスの配列で取得できます。
getMethod()と異なり、すべてのアクセス修飾子(public、protected、デフォルト、private)のメソッドが取得できます。

スーパークラスのメソッドは取得できませんが、オーバーライドしていれば取得可能です。
toString()はオーバーライドしているため取得されます。

テストコードはこんな感じ。

    public static class getDeclaredMethodsの確認 {
        @Test
        public void getDeclaredMethodsでCircleクラスのメソッドが取得できること() throws Exception {
            Class clazz = Circle.class;
            Method[] methods = clazz.getDeclaredMethods();
            Method[] expected = {
                    clazz.getDeclaredMethod("area"),
                    clazz.getDeclaredMethod("area", int.class),
                    clazz.getDeclaredMethod("publicMethod"),
                    clazz.getDeclaredMethod("protectedMethod"),
                    clazz.getDeclaredMethod("defaultMethod"),
                    clazz.getDeclaredMethod("privateMethod"),
                    clazz.getDeclaredMethod("staticMethod"),
                    clazz.getDeclaredMethod("toString")
            };
            assertThat(methods, is(arrayContainingInAnyOrder(expected)));
        }
    }

Methodクラス

メソッド情報取得

Methodクラスのメソッドを利用してメソッドの情報が取得できます。
メソッド名、メソッドが定義されたクラス、戻り値の型、引数の型を取得してみます。

    Method method = Circle.class.getMethod("area");
    // メソッド名取得
    String methodName = method.getName();

    // メソッドが定義されたクラス取得
    Class clazz = method.getDeclaringClass();

    // メソッドの引数の型を取得
    Class<?>[] argClazz = method.getParameterTypes();

    // メソッドの戻り値の型を取得
    Class retClazz = method.getReturnType();

テストコードはこんな感じ。

    public static class Methodクラスでメソッド情報取得 {
        @Test
        public void メソッド名を取得() throws Exception {
            Method method = Circle.class.getMethod("area");
            String methodName = method.getName();
            assertThat(methodName, is("area"));
        }

        @Test
        public void メソッドが定義されたクラスを取得() throws Exception {
            Method method = Circle.class.getMethod("area");
            Class clazz = method.getDeclaringClass();
            assertThat(clazz, is(equalTo(Circle.class)));
        }

        @Test
        public void メソッドの引数の型を取得() throws Exception {
            Method method = Circle.class.getMethod("area", int.class);
            Class<?>[] argClazz = method.getParameterTypes();
            assertThat(argClazz, is(arrayContainingInAnyOrder(int.class)));
        }

        @Test
        public void メソッドの戻り値の型を取得() throws Exception {
            Method method = Circle.class.getMethod("area");
            Class retClazz = method.getReturnType();
            assertThat(retClazz, is(equalTo(int.class)));
        }
    }
invoke()でメソッドコール

invoke()メソッドでメソッドをコールできます。
引数にはオブジェクトを指定し、そのオブジェクトに対してメソッドが実行されます。

    // CircleクラスのpublicMethod()を実行
    Circle circle = Circle.class.newInstance();
    Method method = Circle.class.getMethod("publicMethod");
    method.invoke(circle);

戻り値として、コールしたメソッドの戻り値が戻ります。

    int ret = (int) method.invoke(circle);

引数を取るメソッドをコールする場合、第2引数以降に指定します。

    // Circleクラスのarea(int)メソッドに引数3を与えて実行
    method.invoke(circle, 3);

staticメソッドをコールする場合、第1引数は無視されます。
nullを指定して実行できます。

    // staticメソッドをコールする
    method.invoke(nullValue());

テストコードはこんな感じ。

    public static class invokeでメソッドコール {
        @Test
        public void invokeで引数なしのメソッドを実行() throws Exception {
            Circle circle = Circle.class.newInstance();
            Method method = Circle.class.getMethod("publicMethod");
            int ret = (int) method.invoke(circle);
            assertThat(ret, is(1));
        }

        @Test
        public void invokeで引数ありのメソッドを実行() throws Exception {
            Circle circle = Circle.class.newInstance();
            Method method = Circle.class.getMethod("area", int.class);
            int ret = (int) method.invoke(circle, 3);
            assertThat(ret, is(28));
        }

        @Test
        public void invokeでstaticメソッドを実行() throws Exception {
            Method method = Circle.class.getMethod("staticMethod");
            int ret = (int) method.invoke(nullValue());
            assertThat(ret, is(11));
        }
    }

テストコード

今回のテストコードの全体です。

CircleTest.java

package reflect;

import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;

import java.lang.reflect.Method;

import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
import static org.junit.Assert.*;

/**
 * Created by pppurple on 2016/07/18.
 */
@RunWith(Enclosed.class)
public class CircleTest {

    public static class forNameの確認 {
        @Test
        public void Circleクラスのインスタンスが取得できること() throws Exception {
            Class<?> clazz = Class.forName("reflect.Circle");
            Circle circle = (Circle) clazz.newInstance();
            assertThat(circle, is(instanceOf(Circle.class)));
        }
    }

    public static class getMethodの確認 {
        @Test
        public void getMethodでareaメソッドのMethodオブジェクトが取得できること() throws Exception {
            Method method = Circle.class.getMethod("area");
            assertThat(method.toString(), is("public int reflect.Circle.area()"));
        }

        @Test
        public void getMethodで引数ありのareaメソッドのMethodオブジェクトが取得できること() throws Exception {
            Method method = Circle.class.getMethod("area", int.class);
            assertThat(method.toString(), is("public int reflect.Circle.area(int)"));
        }

        @Test
        public void getMethodでpublicメソッドが取得できること() throws Exception {
            Method pub = Circle.class.getMethod("publicMethod");
            assertThat(pub.toString(), is("public int reflect.Circle.publicMethod()"));
        }

        @Test(expected = NoSuchMethodException.class)
        public void getMethodでprotectedメソッドが取得できないこと() throws Exception {
            Method pro = Circle.class.getMethod("protectedMethod");
            assertThat(pro.toString(), is("protected int reflect.Circle.protectedMethod()"));
        }

        @Test(expected = NoSuchMethodException.class)
        public void getMethodでdefaultメソッドが取得できないこと() throws Exception {
            Method def = Circle.class.getMethod("defaultMethod");
            assertThat(def.toString(), is("int reflect.Circle.defaultMethod()"));
        }

        @Test(expected = NoSuchMethodException.class)
        public void getMethodでprivateメソッドが取得できないこと() throws Exception {
            Method pri = Circle.class.getMethod("privateMethod");
            assertThat(pri.toString(), is("private int reflect.Circle.privateMethod()"));
        }

        @Test
        public void getMethodでhashCodeメソッドが取得できること() throws Exception {
            Method method = Circle.class.getMethod("hashCode");
            assertThat(method.toString(), is("public native int java.lang.Object.hashCode()"));
        }
    }

    public static class getMethodsの確認 {
        @Test
        public void getMethodsでCircleクラスとスーパークラスのメソッドが取得できること() throws Exception {
            Class clazz = Circle.class;
            Method[] methods = clazz.getMethods();
            Method[] expected = {
                    clazz.getMethod("area"),
                    clazz.getMethod("area", int.class),
                    clazz.getMethod("publicMethod"),
                    clazz.getMethod("staticMethod"),
                    clazz.getMethod("toString"),
                    clazz.getMethod("wait"),
                    clazz.getMethod("wait", long.class),
                    clazz.getMethod("wait", long.class, int.class),
                    clazz.getMethod("equals", Object.class),
                    clazz.getMethod("hashCode"),
                    clazz.getMethod("getClass"),
                    clazz.getMethod("notify"),
                    clazz.getMethod("notifyAll")
            };
            assertThat(methods, is(arrayContainingInAnyOrder(expected)));
        }
    }

    public static class getDeclaredMethodの確認 {
        @Test
        public void getDeclaredMethodでareaメソッドが取得できること() throws Exception {
            Method method = Circle.class.getDeclaredMethod("area");
            assertThat(method.toString(), is("public int reflect.Circle.area()"));
        }

        @Test
        public void getDeclaredMethodでpublicメソッドが取得できること() throws Exception {
            Method pub = Circle.class.getDeclaredMethod("publicMethod");
            assertThat(pub.toString(), is("public int reflect.Circle.publicMethod()"));
        }

        @Test
        public void getDeclaredMethodでprotectedメソッドが取得できること() throws Exception {
            Method pro = Circle.class.getDeclaredMethod("protectedMethod");
            assertThat(pro.toString(), is("protected int reflect.Circle.protectedMethod()"));
        }

        @Test
        public void getDeclaredMethodでdefaultメソッドが取得できること() throws Exception {
            Method def = Circle.class.getDeclaredMethod("defaultMethod");
            assertThat(def.toString(), is("int reflect.Circle.defaultMethod()"));
        }

        @Test
        public void getDeclaredMethodでprivateメソッドが取得できること() throws Exception {
            Method pri = Circle.class.getDeclaredMethod("privateMethod");
            assertThat(pri.toString(), is("private int reflect.Circle.privateMethod()"));
        }

        @Test
        public void getDeclaredMethodでオーバーライドメソッドが取得できること() throws Exception {
            Method toStr = Circle.class.getDeclaredMethod("toString");
            assertThat(toStr.toString(), is("public java.lang.String reflect.Circle.toString()"));
        }

        @Test(expected = NoSuchMethodException.class)
        public void getDeclaredMethodでhashCodeメソッドが取得できないこと() throws Exception {
            Method method = Circle.class.getDeclaredMethod("hashCode");
            assertThat(method.toString(), is("public native int java.lang.Object.hashCode()"));
        }
    }

    public static class getDeclaredMethodsの確認 {
        @Test
        public void getDeclaredMethodsでCircleクラスのメソッドが取得できること() throws Exception {
            Class clazz = Circle.class;
            Method[] methods = clazz.getDeclaredMethods();
            Method[] expected = {
                    clazz.getDeclaredMethod("area"),
                    clazz.getDeclaredMethod("area", int.class),
                    clazz.getDeclaredMethod("publicMethod"),
                    clazz.getDeclaredMethod("protectedMethod"),
                    clazz.getDeclaredMethod("defaultMethod"),
                    clazz.getDeclaredMethod("privateMethod"),
                    clazz.getDeclaredMethod("staticMethod"),
                    clazz.getDeclaredMethod("toString")
            };
            assertThat(methods, is(arrayContainingInAnyOrder(expected)));
        }
    }

    public static class Methodクラスでメソッド情報取得 {
        @Test
        public void メソッド名を取得() throws Exception {
            Method method = Circle.class.getMethod("area");
            String methodName = method.getName();
            assertThat(methodName, is("area"));
        }

        @Test
        public void メソッドが定義されたクラスを取得() throws Exception {
            Method method = Circle.class.getMethod("area");
            Class clazz = method.getDeclaringClass();
            assertThat(clazz, is(equalTo(Circle.class)));
        }

        @Test
        public void メソッドの引数の型を取得() throws Exception {
            Method method = Circle.class.getMethod("area", int.class);
            Class<?>[] argClazz = method.getParameterTypes();
            assertThat(argClazz, is(arrayContainingInAnyOrder(int.class)));
        }

        @Test
        public void メソッドの戻り値の型を取得() throws Exception {
            Method method = Circle.class.getMethod("area");
            Class retClazz = method.getReturnType();
            assertThat(retClazz, is(equalTo(int.class)));
        }
    }

    public static class invokeでメソッドコール {
        @Test
        public void invokeで引数なしのメソッドを実行() throws Exception {
            Circle circle = Circle.class.newInstance();
            Method method = Circle.class.getMethod("publicMethod");
            int ret = (int) method.invoke(circle);
            assertThat(ret, is(1));
        }

        @Test
        public void invokeで引数ありのメソッドを実行() throws Exception {
            Circle circle = Circle.class.newInstance();
            Method method = Circle.class.getMethod("area", int.class);
            int ret = (int) method.invoke(circle, 3);
            assertThat(ret, is(28));
        }

        @Test
        public void invokeでstaticメソッドを実行() throws Exception {
            Method method = Circle.class.getMethod("staticMethod");
            int ret = (int) method.invoke(nullValue());
            assertThat(ret, is(11));
        }
    }
}

ソースはあげておきました。
https://github.com/pppurple/java_examples/tree/master/reflection_methodgithub.com

終わり。