web-dev-qa-db-ja.com

Java 8メソッド参照のMethodInfoを取得するには?

次のコードをご覧ください。

Method methodInfo = MyClass.class.getMethod("myMethod");

これは機能しますが、メソッド名は文字列として渡されるため、myMethodが存在しなくてもコンパイルされます。

一方、Java 8はメソッド参照機能を導入しています。コンパイル時にチェックされます。この機能を使用してメソッド情報を取得できますか?

printMethodName(MyClass::myMethod);

完全な例:

@FunctionalInterface
private interface Action {

    void invoke();
}

private static class MyClass {

    public static void myMethod() {
    }
}

private static void printMethodName(Action action) {
}

public static void main(String[] args) throws NoSuchMethodException {
    // This works, but method name is passed as a string, so this will compile
    // even if myMethod does not exist
    Method methodInfo = MyClass.class.getMethod("myMethod");

    // Here we pass reference to a method. It is somehow possible to
    // obtain Java.lang.reflect.Method for myMethod inside printMethodName?
    printMethodName(MyClass::myMethod);
}

つまり、次のC#コードに相当するコードが必要です。

    private static class InnerClass
    {
        public static void MyMethod()
        {
            Console.WriteLine("Hello");
        }
    }

    static void PrintMethodName(Action action)
    {
        // Can I get Java.lang.reflect.Method in the same way?
        MethodInfo methodInfo = action.GetMethodInfo();
    }

    static void Main()
    {
        PrintMethodName(InnerClass.MyMethod);
    }
67
Rafal

いいえ、これを行うための信頼できるサポートされた方法はありません。メソッド参照を機能インターフェイスのインスタンスに割り当てますが、そのインスタンスはLambdaMetaFactoryによって作成され、最初にバインドしたメソッドを見つけるためにドリルする方法はありません。

Javaのラムダとメソッド参照は、C#のデリゲートとはまったく異なる動作をします。興味深い背景については、invokedynamicを参照してください。

ここでの他の回答とコメントは、現在、追加の作業を行うことでバインドされたメソッドを取得できる可能性があることを示していますが、警告を理解してください。

28
Mike Strobel

プロキシを使用して、どのメソッドが呼び出されるかを記録することで、結局は可能になるようです。

https://stackoverflow.com/a/22745127/3478229

14
ddan

私の場合、ユニットテストでこれを取り除く方法を探していました:

Point p = getAPoint();
assertEquals(p.getX(), 4, "x");
assertEquals(p.getY(), 6, "x");

ご覧のとおり、誰かがMethod getAPointをテストし、座標が予想どおりであることを確認していますが、各アサートの説明ではコピーされ、チェック内容と同期していません。これは1回だけ記述する方が良いでしょう。

@ddanのアイデアから、Mockitoを使用してプロキシソリューションを構築しました。

private<T> void assertPropertyEqual(final T object, final Function<T, ?> getter, final Object expected) {
    final String methodName = getMethodName(object.getClass(), getter);
    assertEquals(getter.apply(object), expected, methodName);
}

@SuppressWarnings("unchecked")
private<T> String getMethodName(final Class<?> clazz, final Function<T, ?> getter) {
    final Method[] method = new Method[1];
    getter.apply((T)Mockito.mock(clazz, Mockito.withSettings().invocationListeners(methodInvocationReport -> {
        method[0] = ((InvocationOnMock) methodInvocationReport.getInvocation()).getMethod();
    })));
    return method[0].getName();
}

いいえ、単純に使用できます

assertPropertyEqual(p, Point::getX, 4);
assertPropertyEqual(p, Point::getY, 6);

また、アサートの記述はコードと同期していることが保証されています。

欠点:

  • 上記よりわずかに遅くなります
  • 動作するにはMockitoが必要
  • 上記のユースケース以外ではほとんど役に立ちません。

ただし、その方法を示しています。

8
yankee

私は自分で試したことはありませんが、答えは「いいえ」だと思います メソッド参照は意味的にはラムダと同じです

6
Matt Ball

インターフェイスをAction拡張Serializableにできる場合、別の質問から この答え が解決策を提供しているように見えます(少なくとも一部のコンパイラとランタイムでは)。

3
user102008

safety-mirror をクラスパスに追加して、次のようにすることができます。

_Method m1 = Types.createMethod(Thread::isAlive)  // Get final method
Method m2 = Types.createMethod(String::isEmpty); // Get method from final class
Method m3 = Types.createMethod(BufferedReader::readLine); // Get method that throws checked exception
Method m4 = Types.<String, Class[]>createMethod(getClass()::getDeclaredMethod); //to get vararg method you must specify parameters in generics
Method m5 = Types.<String>createMethod(Class::forName); // to get overloaded method you must specify parameters in generics
Method m6 = Types.createMethod(this::toString); //Works with inherited methods
_

ライブラリには、getName(...)メソッドも用意されています。

_assertEquals("isEmpty", Types.getName(String::isEmpty));
_

ライブラリは、ホルガーの答えに基づいています: https://stackoverflow.com/a/21879031/6095334

編集:ライブラリには、私が徐々に気づいているさまざまな欠点があります。 fx Holgerのコメントを参照してください: ラムダから生じるメソッドの名前を取得する方法

3
Hervian

メソッド名をキャプチャするために使用できる小さなライブラリ de.cronn:reflection-util を公開しました。

例:

class MyClass {

    public void myMethod() {
    }

}

String methodName = ClassUtils.getVoidMethodName(MyClass.class, MyClass::myMethod);
System.out.println(methodName); // prints "myMethod"

実装の詳細:MyClassのProxyサブクラスが ByteBuddy で作成され、メソッドの呼び出しがキャプチャされてその名前が取得されます。 ClassUtilsは情報をキャッシュするため、呼び出しごとに新しいプロキシを作成する必要はありません。

このアプローチは、staticメソッドでは機能しないことに注意してください。

2

信頼できる方法はないかもしれませんが、状況によっては:

  1. MyClassは最終的なものではなく、アクセス可能なコンストラクターがあります(cglibの制限)
  2. myMethodはオーバーロードされておらず、静的でもありません

Cglibを使用してMyClassのプロキシを作成してから、MethodInterceptorを使用してMethodを報告し、次の試行実行でメソッド参照を呼び出してみてください。

サンプルコード:

public static void main(String[] args) {
    Method m = MethodReferenceUtils.getReferencedMethod(ArrayList.class, ArrayList::contains);
    System.out.println(m);
}

次の出力が表示されます。

public boolean Java.util.ArrayList.contains(Java.lang.Object)

一方:

public class MethodReferenceUtils {

    @FunctionalInterface
    public static interface MethodRefWith1Arg<T, A1> {
        void call(T t, A1 a1);
    }

    public static <T, A1> Method getReferencedMethod(Class<T> clazz, MethodRefWith1Arg<T, A1> methodRef) {
        return findReferencedMethod(clazz, t -> methodRef.call(t, null));
    }

    @SuppressWarnings("unchecked")
    private static <T> Method findReferencedMethod(Class<T> clazz, Consumer<T> invoker) {
        AtomicReference<Method> ref = new AtomicReference<>();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                ref.set(method);
                return null;
            }
        });
        try {
            invoker.accept((T) enhancer.create());
        } catch (ClassCastException e) {
            throw new IllegalArgumentException(String.format("Invalid method reference on class [%s]", clazz));
        }

        Method method = ref.get();
        if (method == null) {
            throw new IllegalArgumentException(String.format("Invalid method reference on class [%s]", clazz));
        }

        return method;
    }
}

上記のコードでは、MethodRefWith1Argは、1つの引数を持つ非静的メソッドを参照するための単なる構文シュガーです。他のメソッドを参照するために、MethodRefWithXArgsを作成できます。

1
vivimice

ライブラリを使用できます Reflect Without String

Method myMethod = ReflectWithoutString.methodGetter(MyClass.class).getMethod(MyClass::myMethod);
0
Dean Xu