次のコードをご覧ください。
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);
}
いいえ、これを行うための信頼できるサポートされた方法はありません。メソッド参照を機能インターフェイスのインスタンスに割り当てますが、そのインスタンスはLambdaMetaFactory
によって作成され、最初にバインドしたメソッドを見つけるためにドリルする方法はありません。
Javaのラムダとメソッド参照は、C#のデリゲートとはまったく異なる動作をします。興味深い背景については、invokedynamic
を参照してください。
ここでの他の回答とコメントは、現在、追加の作業を行うことでバインドされたメソッドを取得できる可能性があることを示していますが、警告を理解してください。
プロキシを使用して、どのメソッドが呼び出されるかを記録することで、結局は可能になるようです。
私の場合、ユニットテストでこれを取り除く方法を探していました:
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);
また、アサートの記述はコードと同期していることが保証されています。
欠点:
ただし、その方法を示しています。
私は自分で試したことはありませんが、答えは「いいえ」だと思います メソッド参照は意味的にはラムダと同じです
インターフェイスをAction
拡張Serializable
にできる場合、別の質問から この答え が解決策を提供しているように見えます(少なくとも一部のコンパイラとランタイムでは)。
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のコメントを参照してください: ラムダから生じるメソッドの名前を取得する方法
メソッド名をキャプチャするために使用できる小さなライブラリ 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メソッドでは機能しないことに注意してください。
信頼できる方法はないかもしれませんが、状況によっては:
MyClass
は最終的なものではなく、アクセス可能なコンストラクターがあります(cglibの制限)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
を作成できます。
ライブラリを使用できます Reflect Without String
Method myMethod = ReflectWithoutString.methodGetter(MyClass.class).getMethod(MyClass::myMethod);