1. 概要

この短い記事では、Java ReflectionAPIを使用して実行時にメソッドを呼び出す方法を簡単に説明します。

2. 準備

次の例で使用する簡単なクラスを作成しましょう。

public class Operations {
    public double publicSum(int a, double b) {
        return a + b;
    }

    public static double publicStaticMultiply(float a, long b) {
        return a * b;
    }

    private boolean privateAnd(boolean a, boolean b) {
        return a && b;
    }

    protected int protectedMax(int a, int b) {
        return a > b ? a : b;
    }
}

3. メソッドオブジェクトの取得

まず、呼び出したいメソッドを反映するMethodオブジェクトを取得する必要があります。 メソッドが定義されているタイプを表すClassオブジェクトは、これを行う2つの方法を提供します。

3.1. getMethod()

getMethod()を使用して、クラスまたはそのスーパークラスのパブリックメソッドを見つけることができます。

基本的に、最初の引数としてメソッド名を受け取り、その後にメソッドの引数のタイプを受け取ります。

Method sumInstanceMethod
  = Operations.class.getMethod("publicSum", int.class, double.class);

Method multiplyStaticMethod
  = Operations.class.getMethod(
    "publicStaticMultiply", float.class, long.class);

3.2. getDeclaredMethod()

getDeclaredMethod()を使用して、あらゆる種類のメソッドを取得できます。 これには、パブリック、プロテクト、デフォルトアクセス、さらにはプライベートメソッドが含まれますが、継承されたメソッドは除外されます。

getMethod()と同じパラメーターを受け取ります。

Method andPrivateMethod
  = Operations.class.getDeclaredMethod(
    "privateAnd", boolean.class, boolean.class);
Method maxProtectedMethod
  = Operations.class.getDeclaredMethod("protectedMax", int.class, int.class);

4. メソッドの呼び出し

Method インスタンスが配置されたら、 invoke()を呼び出して、基になるメソッドを実行し、返されたオブジェクトを取得できます。

4.1. インスタンスメソッド

インスタンスメソッドを呼び出すには、 invoke()の最初の引数は、呼び出されるメソッドを反映するMethodのインスタンスである必要があります。

@Test
public void givenObject_whenInvokePublicMethod_thenCorrect() {
    Method sumInstanceMethod
      = Operations.class.getMethod("publicSum", int.class, double.class);

    Operations operationsInstance = new Operations();
    Double result
      = (Double) sumInstanceMethod.invoke(operationsInstance, 1, 3);

    assertThat(result, equalTo(4.0));
}

4.2. 静的メソッド

これらのメソッドはインスタンスを呼び出す必要がないため、最初の引数としてnullを渡すことができます。

@Test
public void givenObject_whenInvokeStaticMethod_thenCorrect() {
    Method multiplyStaticMethod
      = Operations.class.getDeclaredMethod(
        "publicStaticMultiply", float.class, long.class);

    Double result
      = (Double) multiplyStaticMethod.invoke(null, 3.5f, 2);

    assertThat(result, equalTo(7.0));
}

5. メソッドのアクセシビリティ

デフォルトでは、反映されたすべてのメソッドにアクセスできるわけではありません。これは、JVMがそれらを呼び出すときにアクセス制御チェックを実施することを意味します。

たとえば、定義クラスの外部のプライベートメソッド、またはサブクラスまたはそのクラスのパッケージの外部から保護されたメソッドを呼び出そうとすると、IllegalAccessExceptionが発生します。

@Test(expected = IllegalAccessException.class)
public void givenObject_whenInvokePrivateMethod_thenFail() {
    Method andPrivateMethod
      = Operations.class.getDeclaredMethod(
        "privateAnd", boolean.class, boolean.class);

    Operations operationsInstance = new Operations();
    Boolean result
      = (Boolean) andPrivateMethod.invoke(operationsInstance, true, false);

    assertFalse(result);
}

@Test(expected = IllegalAccessException.class)
public void givenObject_whenInvokeProtectedMethod_thenFail() {
    Method maxProtectedMethod
      = Operations.class.getDeclaredMethod(
        "protectedMax", int.class, int.class);

    Operations operationsInstance = new Operations();
    Integer result
      = (Integer) maxProtectedMethod.invoke(operationsInstance, 2, 4);
    
    assertThat(result, equalTo(4));
}

5.1. AccessibleObject setAccesible

リフレクトされたメソッドオブジェクトでsetAccesible(true)を呼び出すことにより、JVMはアクセス制御チェックを抑制し、例外をスローせずにメソッドを呼び出すことができます。

@Test
public void givenObject_whenInvokePrivateMethod_thenCorrect() throws Exception {
    Method andPrivatedMethod = Operations.class.getDeclaredMethod("privateAnd", boolean.class, boolean.class);
    andPrivatedMethod.setAccessible(true);

    Operations operationsInstance = new Operations();
    Boolean result = (Boolean) andPrivatedMethod.invoke(operationsInstance, true, false);

    assertFalse(result);
}

5.2. AccessibleObject#canAccess

Java 9 には、呼び出し元がリフレクトされたメソッドオブジェクトにアクセスできるかどうかをチェックするまったく新しい方法が付属しています。

この目的のために、非推奨のメソッドisAccessibleの代わりにcanAccessを提供します。

実際の動作を見てみましょう。

@Test
public void givenObject_whenInvokePrivateMethod_thenCheckAccess() throws Exception {
    Operations operationsInstance = new Operations();
    Method andPrivatedMethod = Operations.class.getDeclaredMethod("privateAnd", boolean.class, boolean.class);
    boolean isAccessEnabled = andPrivatedMethod.canAccess(operationsInstance);
 
    assertFalse(isAccessEnabled);
 }

canAccess を使用して、 ccessibleフラグをsetAccessible(true)で true に設定する前に、呼び出し元がリフレクトされたメソッドにすでにアクセスできるかどうかを確認できます。

5.3. AccessibleObject#trySetAccessible

trySetAccessible は、反映されたオブジェクトにアクセスできるようにするために使用できるもう1つの便利なメソッドです。

この新しいメソッドの良いところは、アクセスを有効にできない場合はfalseを返すことです。 ただし、古いメソッド setAccessible(true)は、失敗するとInaccessibleObjectExceptionをスローします。

trySetAccessibleメソッドの使用例を見てみましょう。

@Test
public void givenObject_whenInvokePublicMethod_thenEnableAccess() throws Exception {
    Operations operationsInstance = new Operations();
    Method andPrivatedMethod = Operations.class.getDeclaredMethod("privateAnd", boolean.class, boolean.class);
    andPrivatedMethod.trySetAccessible();
    boolean isAccessEnabled = andPrivatedMethod.canAccess(operationsInstance);
        
    assertTrue(isAccessEnabled);
}

6. 結論

この簡単な記事では、リフレクションを使用して実行時にクラスのインスタンスメソッドと静的メソッドを呼び出す方法を説明しました。 また、リフレクトされたメソッドオブジェクトのアクセス可能なフラグを変更して、プライベートメソッドと保護されたメソッドを呼び出すときにJavaアクセス制御チェックを抑制する方法も示しました。

いつものように、サンプルコードはGithubにあります。