0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Spring ReflectionUtils.findMethod徹底解説:IDEの警告と実際の挙動

Posted at

はじめに

Java でリフレクションを使うとき、Class#getMethod や Class#getDeclaredMethod を直接呼ぶのが一般的です。
しかし、この書き方は例外処理やアクセス制御の対応が必要で、コードが煩雑になりがち。

Spring Framework はこうしたリフレクション操作をシンプルに扱えるユーティリティクラス ReflectionUtils を提供しています。

その中でも特によく使われるのが findMethod です。

本記事では、その挙動と活用方法、筆者がfindMethodを書いていてIDEで出力されていた「常に true」警告について詳しく解説します。

ReflectionUtils とは

org.springframework.util.ReflectionUtils は、Spring Framework が提供する静的メソッド群を集めたクラスです。標準 Java のリフレクション API をラップして、例外処理や面倒な記述を減らすのが目的です。

例えば以下のような機能があります。

  • findMethod : 指定クラスからメソッドを探す
  • invokeMethod : メソッドを実行する
  • findField : フィールドを探す
  • setField : フィールドに値をセットする

つまり、ReflectionUtils は「便利リフレクションヘルパー」として活用できるクラスなのです。

findMethod のシグネチャ

public static Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes)

引数

  • clazz : メソッドを探索する対象のクラス
  • name : メソッド名(例 "setId")
  • paramTypes : メソッドの引数の型(複数ある場合はカンマ区切り)

戻り値

  • Method : 見つかった場合、そのメソッドオブジェクト
  • null : 見つからなかった場合

挙動の特徴

  1. スーパークラスまで探索する

    サブクラスに setId がなくても、スーパークラスにあれば見つけてくれます。標準の getDeclaredMethod はサブクラスだけを見るのに対し、findMethod は階層をたどって探してくれるため、実務で使いやすいです。

  2. 存在しなければ null

    見つからない場合は null を返します。
    標準 API の getMethod だと NoSuchMethodException が投げられますが、findMethod は例外を避けられるのがポイントです。

    Method method = ReflectionUtils.findMethod(obj.getClass(), "unknownMethod");
    if (method == null) {
        System.out.println("メソッドが存在しません");
    }
    
  3. アクセス修飾子は無視

    private メソッドでも検索対象になります。invokeMethod を使うと、自動的に setAccessible(true) が実行されるため、通常のリフレクションより扱いやすいです。

    // 基本的な使い方
    // Getter を呼ぶ
    Method method = ReflectionUtils.findMethod(obj.getClass(), "getId");
    if (method != null) {
        Object value = ReflectionUtils.invokeMethod(method, obj);
        System.out.println("id = " + value);
    }
    
    // Setter を呼ぶ
    Method method = ReflectionUtils.findMethod(obj.getClass(), "setId", Integer.class);
    if (method != null) {
        ReflectionUtils.invokeMethod(method, obj, 100);
    }

実務での利用シーン

  1. AOP で引数オブジェクトを処理

    Object arg = jp.getArgs()[0];
    Method getId = ReflectionUtils.findMethod(arg.getClass(), "getId");
    if (getId != null) {
        Integer id = (Integer) ReflectionUtils.invokeMethod(getId, arg);
        System.out.println("取得したID: " + id);
    }
    
  2. テストで private メソッドを呼びたいとき

    Method hidden = ReflectionUtils.findMethod(MyService.class, "calculateSecretValue");
    ReflectionUtils.makeAccessible(hidden);
    Object result = ReflectionUtils.invokeMethod(hidden, serviceInstance);
    

IDE が「if (method != null) は常に true」と警告する理由

実務で直面したのが下記のようなコード。

Method method = ReflectionUtils.findMethod(arg.getClass(), "setId", Integer.class);
if (method != null) {
    ReflectionUtils.invokeMethod(method, arg, 123);
}

ここで IDE(IntelliJ IDEA など)が「条件は常に true」や「冗長な null チェック」と警告することがあります。

なぜか

IDE が型情報やコード解析を通じて
「このクラスには必ず setId(Integer) がある」と判断できた

そのため findMethod の結果が null になる可能性はゼロだとみなしている

実際の挙動は?

findMethod 自体は仕様上 null を返す可能性がある メソッドです。
ただし、MyBatis Generator が生成した POJO のように「必ず getId/setId が作られる」ことが分かっている場面では、実質 null にならないため IDE が「常に true」と言っているのです。

null チェックは必要?

ケースバイケースです。

必要な場合

  • 複数種類のクラスを扱い、必ずしも対象メソッドが存在するとは限らないとき
  • 外部ライブラリや別チームのコードを相手にするとき

不要な場合

  • 自動生成コードやフレームワークによって、必ずメソッドが存在することが分かっているとき
  • IDE 警告を消してコードをシンプルにしたいとき

つまり「対象クラスに必ずある」と分かっているならチェック不要、そうでなければチェック推奨です。

注意点

  • リフレクションは通常呼び出しより遅い
  • 型安全ではないため、キャストミスに注意
  • 可能であれば一度取得した Method をキャッシュするのが望ましい

まとめ

ReflectionUtils.findMethod は「安全に探す」「例外を投げない」「コードをすっきり書ける」という三拍子そろったユーティリティです。

IDE が「null チェックは不要」と教えてくれる場面もありますが、仕様的には null を返し得るため、最終判断はユースケース次第です。

必要なところで使い、不要なところでは潔く書かない。
そのメリハリこそ、リフレクションと上手に付き合うコツと言えるのではないでしょうか。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?