[Java] 30. Reflection機能を使う方法(Method編)


Study / Java    作成日付 : 2019/09/19 20:20:10   修正日付 : 2021/03/12 13:04:09

こんにちは。明月です。


この投稿はJavaのReflection機能を使う方法(Method編)に関する説明です。


以前の投稿でReflection機能のクラスを説明したことがあります。

link - [Java] 29. Reflection機能を使う方法(Class編)


Reflectionの機能はクラスの構造を分析してメソッドや変数を使える機能だと説明しました。

そうするとReflectionの動的の割り当てでクラスをObjectに格納するとObjectクラスの関数だけではなくReflection機能を通ってクラス構造を分析してメソッドを探索して使えます。

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
// ノードクラス
class Node {
  // コンストラクタ
  public Node() { }
  // 関数生成
  public void print() {
    // コンソール出力
    System.out.println("Hello world");
  }
}
public class Example {
  // 実行関数
  public static void main(String... args) {
    try {
      // Nodeクラスのタイプを探す。
      Class<?> cls = Class.forName("Node");
      // Nodeクラスのコンストラクタを取得する。
      Constructor<?> constructor = cls.getConstructor();
      // コンストラクタを通ってnewInstance関数を呼び出し、Nodeインスタンスを生成する。
      Object node = constructor.newInstance();
      // Nodeクラスのprint関数を取得する。
      Method method = cls.getMethod("print");
      // 取得した関数に生成したインスタンスを格納して実行する。
      method.invoke(node);
    } catch (Throwable e) {
      e.printStackTrace();
    }
  }
}


上の例でNodeクラスを取得してインスタンスを生成しました。そしてNodeクラスでprintメソッドを探してメソッドにインスタンスを入れて実行(invoke)します。

main関数でソース上ではNodeキーワードを使えなかったので、Nodeクラスを動的に分離することができます。


Javaにはメソッドのタイプが関数の種類が2つです。コンストラクタと一般関数に分けます。

コンストラクタの場合は返却タイプがない形です。JavaのReflectionではgetConstructor()関数を使って取得できます。

一般関数の場合は返却タイプがある形です。JavaのReflectionではgetMethod()関数を使って取得できます。

関数を探索する時に関数の名前も重要ですが、Javaにはオバーロード機能があるので、パラメータの個数とタイプも重要です。

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
// ノードクラス
class Node {
  // コンストラクタ
  public Node() {  }
  // 関数生成 (パラメータなし)
  public void print() {
    // コンソール出力
    System.out.println("print");
  }
  // 関数生成 (パラメータがStringタイプで一つ)
  public void print(String msg) {
    // コンソール出力
    System.out.println("print " + msg);
  }
  // 関数生成(パラメータがString、intタイプで二つ)
  public void print(String msg, int count) {
    // コンソール出力
    System.out.println("print " + msg + " count - " + count);
  }
}
public class Example {
  // 実行関数
  public static void main(String... args) {
    try {
      // Nodeクラスのタイプを探す。
      Class<?> cls = Class.forName("Node");
      // Nodeクラスのコンストラクタを取得する。
      Constructor<?> constructor = cls.getConstructor();
      // コンストラクタを通ってnewInstance関数を呼び出し、Nodeインスタンスを生成する。
      Object node = constructor.newInstance();
      // Nodeクラスからパラメータがないprint関数を取得する。
      Method method = cls.getMethod("print");
      // 取得した関数に生成したインスタンスを入れて実行する。
      method.invoke(node);
      // NodeクラスからStringパラメータを持つprint関数を取得する。
      method = cls.getMethod("print", String.class);
      // 取得した関数に生成したインスタンスを入れて実行する。
      method.invoke(node, "test");
      // NodeクラスからString、intパラメータを持つprint関数を取得する。
      method = cls.getMethod("print", String.class, Integer.TYPE);
      // 取得した関数に生成したインスタンスを入れて実行する。
      method.invoke(node, "test2", 100);
    } catch (Throwable e) {
      e.printStackTrace();
    }
  }
}


上みたいに同じ関数名でもパラメータ設定より呼び出しを別々にすることができます。getConstructor関数も同じ方法でコンストラクタを探索してインスタンスを生成することができます。


Class編でクラスをStringタイプで探索してインスタンスを生成することができました。メソッドも同じ方法でStringタイプで探索をして実行することができます。

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
// ノードクラス
class Node {
  // メンバー変数
  private String data = "init";
  // コンストラクタ
  public Node() {
    // コンソール出力
    System.out.println("Node() constructor");
  }
  // Stringパラメータがあるコンストラクタ
  public Node(String data) {
    // メンバー変数に入力
    this.data = data;
    // コンソール出力
    System.out.println("Node(String) constructor");
  }
  // 出力関数
  public void print() {
    // コンソール出力
    System.out.println("data - " + data);
  }
  // メンバー変数値を修正関数
  public void setData(String data) {
    // メンバー変数値を修正
    this.data = data;
  }
  // メンバー変数値を返却関数
  public String getData() {
    // メンバー変数値を返却
    return this.data;
  }
}
public class Example {
  // クラス取得関数
  private static Object getClass(String name, Object... args) {
    try {
      // Class.forNameの関数を使って文字列でClass<?>タイプを取得する。
      Class<?> clz = Class.forName(name);
      // パラメータをObjectタイプの可変に受け取ったのでパラメータのデータタイプを探す。
      Class<?>[] params = new Class<?>[args.length];
      // パラメータの個数ほど
      for (int i = 0; i < args.length; i++) {
        // タイプ抽出
        params[i] = args[i].getClass();
      }
      // パラメータタイプによるコンストラクタを取得する。
      Constructor<?> constructor = clz.getConstructor(params);
      // コンストラクタを通ってnewInstance関数を呼び出すNodeインスタンスを生成する。
      return constructor.newInstance(args);
    } catch (Throwable e) {
      return null;
    }
  }
  // 関数を探索して実行関数
  private static <T> T executeMethod(Object instance, String name, Object... args) {
    try {
      // パラメータを可変に受け取ったのでパラメータのデータタイプを探す。
      Class<?>[] params = new Class<?>[args.length];
      // パラメータの個数ほど
      for (int i = 0; i < args.length; i++) {
        // タイプ抽出
        params[i] = args[i].getClass();
      }
      // インスタンスのクラスタイプでメソッドを探す。
      Method method = instance.getClass().getMethod(name, params);
      // 実行
      return (T) method.invoke(instance, args);
    } catch (Throwable e) {
      return null;
    }
  }
  // 実行関数
  public static void main(String... args) {
    // Nodeインスタンスを実行する。パラメータがStringのコンストラクタを実行する。
    Object instance = getClass("Node", "hello world");
    // print関数実行
    executeMethod(instance, "print");
    // setData関数実行、パラメータ値を入れる。
    executeMethod(instance, "setData", "good");
    // getData関数を実行してメンバー変数の値を受け取る。
    String data = executeMethod(instance, "getData");
    // コンソール出力
    System.out.println("Node data - " + data);
  }
}


上みたいにStringタイプに関数を探索して実行することも可能です。

この意味はプログラムの実行順番をプログラムの中で実装することではなく、テキストや環境変数によりプログラム実行順番を実装こともできます。

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
// ノードクラス
class Node {
  // メンバー変数
  private String data = "init";
  // コンストラクタ
  public Node() {
    // コンソール出力
    System.out.println("Node() constructor");
  }
  // Stringパラメータがあるコンストラクタ
  public Node(String data) {
    // メンバー変数に入力
    this.data = data;
    // コンソール出力
    System.out.println("Node(String) constructor");
  }
  // 出力関数
  public void print() {
    // コンソール出力
    System.out.println("data - " + data);
  }
  // メンバー変数値を修正関数
  public void setData(String data) {
    // メンバー変数値を修正
    this.data = data;
  }
  // メンバー変数値を返却関数
  public String getData() {
    // メンバー変数値を返却
    return this.data;
  }
}
public class Example {
  // クラス取得関数
  private static Object getClass(String name, Object... args) {
    try {
      // Class.forNameの関数を使って文字列でClass<?>タイプを取得する。
      Class<?> clz = Class.forName(name);
      // パラメータをObjectタイプの可変に受け取ったのでパラメータのデータタイプを探す。
      Class<?>[] params = new Class<?>[args.length];
      // パラメータの個数ほど
      for (int i = 0; i < args.length; i++) {
        // タイプ抽出
        params[i] = args[i].getClass();
      }
      // パラメータタイプによるコンストラクタを取得する。
      Constructor<?> constructor = clz.getConstructor(params);
      // コンストラクタを通ってnewInstance関数を呼び出すNodeインスタンスを生成する。
      return constructor.newInstance(args);
    } catch (Throwable e) {
      return null;
    }
  }
  // 関数を探索して実行関数
  private static <T> T executeMethod(Object instance, String name, Object... args) {
    try {
      // パラメータを可変に受け取ったのでパラメータのデータタイプを探す。
      Class<?>[] params = new Class<?>[args.length];
      // パラメータの個数ほど
      for (int i = 0; i < args.length; i++) {
        // タイプ抽出
        params[i] = args[i].getClass();
      }
      // インスタンスのクラスタイプでメソッドを探す。
      Method method = instance.getClass().getMethod(name, params);
      // 実行
      return (T) method.invoke(instance, args);
    } catch (Throwable e) {
      return null;
    }
  }
  // 実行関数
  public static void main(String... args) {
    if (args.length > 0) {
      // インスタンスを生成する。
      Object instance = getClass("Node");
      // 関数を実行する。
      executeMethod(instance, args[0]);
    }
  }
}


上の例は簡単に実行関数名をパラメータから受け取って実行することにしましたが、テキストで実行する関数名を順番とおりに作成してIOで読み取って実行することもできます。


Reflectionでは単純にgetMethod関数を利用して探すことだけではなく、getMethodsを通ってクラスにあるメソッドをすべて取得することもできます。

import java.lang.reflect.Method;
// ノードクラス
class Node {
  // コンストラクタ
  public Node() { }
  // 出力関数
  public void print() {
    // コンソール出力
    System.out.println("print");
  }
  // 実行関数
  public void execute() {
    // コンソール出力
    System.out.println("execute");
  }
}
public class Example {
  // 実行関数
  public static void main(String... args) {
    try {
      // Class.forNameの関数を使って文字列でClass<?>タイプを取得する。
      Class<?> clz = Class.forName("Node");
      // コンソール出力
      System.out.println("*** method list ***");
      // Nodeクラスのメソッド取得
      for (Method method : clz.getMethods()) {
        // メソッド名を出力
        System.out.println(method.getName());
      }
      // 改行
      System.out.println();
      // コンソール出力
      System.out.println("*** getDeclaredMethods list ***");
      // Nodeクラスのメソッド取得する。
      for (Method method : clz.getDeclaredMethods()) {
        // メソッド名をコンソールに出力
        System.out.println(method.getName());
      }
    } catch (Throwable e) {
      e.printStackTrace();
    }
  }
}


getMethods関数の場合は、当該なクラスの関数だけではなく、上位クラスからすべての関数、つまりObjectクラスからNodeクラスまでのすべての関数を出力します。

getDeclaredMethodsはNodeクラスにある関数を出力します。

仕様によって使ったらよいと思います。


Reflectionには面白い機能があります。privateやprotectedみたいにアクセスが制限している関数も実行することができます。

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
// ノードクラス
class Node {
  // コンストラクタ
  public Node() { }
  // 出力関数(privateで外部からアクセスができない。)
  private void print() {
    // コンソール出力
    System.out.println("print");
  }
  // 実行関数(privateで外部からアクセスができない。)
  private void execute() {
    // コンソール出力
    System.out.println("execute");
  }
}
public class Example {
  // 実行関数
  public static void main(String... args) {
    try {
      // Class.forNameの関数を使って文字列でClass<?>タイプを取得する。
      Class<?> clz = Class.forName("Node");
      // Nodeクラスのコンストラクタを取得する。
      Constructor<?> constructor = clz.getConstructor();
      // コンストラクタを通ってnewInstance関数を呼び出し、Nodeインスタンスを生成する。
      Object node = constructor.newInstance();
      // 関数を取得する。
      for (Method method : clz.getDeclaredMethods()) {
        // アクセス修飾子を無視してアクセスできるようにする。
        method.setAccessible(true);
        // 実行
        method.invoke(node);
      }
    } catch (Throwable e) {
      e.printStackTrace();
    }
  }
}


上みたいにNodeクラスで関数がprivateになっていてもアクセスができます。

普通に上の方法はUnitTestで関数の値を確認する時に使う方法で、プロジェクトのプログラムを実装する時にはOOPのカプセル化が無力化になるのでお勧めではありません。


ここまでJavaのReflection機能を使う方法(Method編)に関する説明でした。


ご不明なところや間違いところがあればコメントしてください。

最新投稿