【Java】クラスが特定のメソッドを持っているか判定する

# はじめに

Javaのリフレクションを使うと、クラスのメソッドやフィールドに動的にアクセスすることができます。
今回は、クラスが特定の名前のメソッドを持っているかを判定する方法について解説します。

# リフレクションを使ってメソッドを取得する

まず、リフレクションを使ってクラスが特定のメソッドを持っているかを判定する基本的な方法を見てみましょう。

```java
import java.lang.reflect.Method;

public class MethodChecker {

    public static boolean hasMethodA(Class<?> clazz) {
        try {
            // クラスがmethodAという名前のメソッドを持っているかをチェック
            Method method = clazz.getMethod("methodA");
            return method != null;
        } catch (NoSuchMethodException e) {
            // メソッドが見つからなかった場合はfalseを返す
            return false;
        }
    }

    public static void main(String[] args) {
        class TestClass {
            public void methodA() {
                // メソッドの実装
            }
        }

        boolean result = hasMethodA(TestClass.class);
        System.out.println("TestClass has methodA: " + result); // true
    }
}
```

この方法では、`clazz.getMethod("methodA")`を使ってメソッドを取得し、存在するかどうかを判定しています。
しかし、リフレクションを使ってメソッドを取得する操作はコストが高いため、頻繁に呼び出される場合、パフォーマンスに悪影響を与える可能性があります。
これを防ぐために、メソッドの取得結果をキャッシュすることで、パフォーマンスを向上させることができます。

# キャッシュを使ってパフォーマンスを向上させる

リフレクションのコストを削減するために、メソッドの取得結果をキャッシュする方法を紹介します。
ここでは、`ConcurrentHashMap`を使ってキャッシュを実装します。

```java
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class MethodChecker {

    // メソッドの取得結果をキャッシュするためのマップ
    private static final Map<Class<?>, Method> methodACache = new ConcurrentHashMap<>();

    public static boolean hasMethodA(Class<?> clazz) {
        // キャッシュに結果が存在する場合はそれを返す
        return methodACache.computeIfAbsent(clazz, c -> {
            try {
                // クラスがmethodAという名前のメソッドを持っているかをチェック
                return c.getMethod("methodA");
            } catch (NoSuchMethodException e) {
                // メソッドが見つからなかった場合はnullを返す
                return null;
            }
        }) != null;
    }

    public static void invokeMethodA(Object obj) throws Exception {
        Class<?> clazz = obj.getClass();
        Method method = methodACache.get(clazz);

        if (method != null) {
            method.invoke(obj);
        } else {
            throw new NoSuchMethodException("methodA not found in class " + clazz.getName());
        }
    }

    public static void main(String[] args) {
        // テスト用のクラス
        class TestClass {
            public void methodA() {
                System.out.println("methodA is called");
            }
        }

        boolean result = hasMethodA(TestClass.class);
        System.out.println("TestClass has methodA: " + result); // true

        try {
            TestClass testObj = new TestClass();
            invokeMethodA(testObj); // "methodA is called"
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
```

## 解説

1. **キャッシュの定義**:
   - `methodACache`は、クラスとメソッドの存在判定結果をキャッシュするためのマップです。`ConcurrentHashMap`を使うことで、スレッドセーフにキャッシュを操作できます。

2. **`hasMethodA`メソッド**:
   - `computeIfAbsent`メソッドを使って、キャッシュに結果が存在しない場合のみリフレクションを行います。
   - リフレクションを使ってメソッドを取得し、存在するかどうかを判定します。
   - 判定結果をキャッシュに保存し、次回以降はキャッシュから直接取得します。

3. **`invokeMethodA`メソッド**:
   - `methodACache`から直接メソッドを取得し、存在する場合は`method.invoke(obj)`を使ってメソッドを実行します。
   - メソッドが存在しない場合は`NoSuchMethodException`をスローします。

4. **テスト用のクラスとメインメソッド**:
   - `TestClass`というテスト用のクラスを定義し、その中に`methodA`という名前のメソッドを持たせています。
   - `hasMethodA`メソッドを使って、`TestClass`が`methodA`を持っているかを判定し、その結果を出力します。
   - `invokeMethodA`メソッドを使って、`TestClass`のインスタンスの`methodA`を実行します。

# リフレクションを使う際の注意点

:::note warn
**安全性の問題**
リフレクションを使うと、アクセス修飾子を無視してフィールドやメソッドにアクセスできるため、カプセル化が破壊される可能性があります。
:::
:::note warn
**コードの可読性の低下**
リフレクションを使うと、コードが複雑になり、可読性が低下します。メンテナンスが難しくなることがあります。
:::

# まとめ

リフレクションを使ってクラスが特定のメソッドを持っているかを判定し、そのメソッドを実行する方法について解説しました。
リフレクションは高コストな操作ですが、キャッシュを使うことでパフォーマンスを向上させ、効率的なプログラムを実現することができます。