90

请看下面的代码:

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);
    }
4

10 回答 10

38

No, there is no reliable, supported way to do this. You assign a method reference to an instance of a functional interface, but that instance is cooked up by LambdaMetaFactory, and there is no way to drill into it to find the method you originally bound to.

Lambdas and method references in Java work quite differently than delegates in C#. For some interesting background, read up on invokedynamic.

Other answers and comments here show that it may currently be possible to retrieve the bound method with some additional work, but make sure you understand the caveats.

于 2013-11-07T19:55:18.097 回答
11

就我而言,我正在寻找一种在单元测试中摆脱这种情况的方法:

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

如您所见,有人正在测试 MethodgetAPoint并检查坐标是否符合预期,但在每个断言的描述中都被复制并且与检查的内容不同步。最好只写一次。

根据@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 才能工作
  • 除了上面的用例之外,几乎没有什么用处。

然而,它确实展示了如何做到这一点。

于 2016-06-04T14:05:49.280 回答
7

虽然我自己没有尝试过,但我认为答案是“否”,因为方法引用在语义上与 lambda 相同。

于 2013-11-07T19:51:12.370 回答
6

您可以将安全镜像添加到您的类路径并执行以下操作:

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));

该库基于 Holger 的回答:https ://stackoverflow.com/a/21879031/6095334

编辑:图书馆有我慢慢意识到的各种缺点。在此处查看 fx Holger 的评论:如何获取由 lambda 产生的方法的名称

于 2018-11-13T10:29:47.707 回答
4

可能没有可靠的方法,但在某些情况下:

  1. MyClass不是最终的,并且有一个可访问的构造函数(cglib的限制)
  2. myMethod没有超载,也不是静态的

您可以尝试使用 cglib 创建 的代理MyClass,然后使用 anMethodInterceptor来报告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只是一个语法糖,您可以使用一个参数引用非静态方法。您可以创建尽可能多MethodRefWithXArgs的引用其他方法。

于 2017-11-17T05:38:34.290 回答
3

如果您可以使接口Action扩展Serializable,那么来自另一个问题的这个答案似乎提供了一个解决方案(至少在某些编译器和运行时)。

于 2015-04-18T22:14:03.933 回答
2

我们已经发布了可以用来捕获方法名称的小型库反射实用程序。

例子:

class MyClass {

    private int value;

    public void myMethod() {
    }

    public int getValue() {
        return value;
    }

}

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

String getterName = ClassUtils.getMethodName(MyClass.class, MyClass::getValue);
System.out.println(getterName); // prints "getValue"

实现细节:使用ByteBuddyMyClass创建Proxy 子类,并捕获对该方法的调用以检索其名称。 缓存信息,这样我们就不需要在每次调用时都创建一个新的代理。ClassUtils

请注意,这种方法不是灵丹妙药,并且有一些已知情况不起作用:

  • 它不适用于静态方法。
  • 如果课程是final则不起作用。
  • 我们目前不支持所有潜在的方法签名。它应该适用于不带参数的方法,例如 getter 方法。
于 2017-11-15T08:40:34.287 回答
0

你可以使用我的图书馆Reflect Without String

Method myMethod = ReflectWithoutString.methodGetter(MyClass.class).getMethod(MyClass::myMethod);
于 2018-04-24T09:33:11.170 回答
-1

所以,我玩这个代码

import sun.reflect.ConstantPool;

import java.lang.reflect.Method;
import java.util.function.Consumer;

public class Main {
    private Consumer<String> consumer;

    Main() {
        consumer = this::test;
    }

    public void test(String val) {
        System.out.println("val = " + val);
    }

    public void run() throws Exception {
        ConstantPool oa = sun.misc.SharedSecrets.getJavaLangAccess().getConstantPool(consumer.getClass());
        for (int i = 0; i < oa.getSize(); i++) {
            try {
                Object v = oa.getMethodAt(i);
                if (v instanceof Method) {
                    System.out.println("index = " + i + ", method = " + v);
                }
            } catch (Exception e) {
            }
        }
    }

    public static void main(String[] args) throws Exception {
        new Main().run();
    }
}

这段代码的输出是:

index = 30, method = public void Main.test(java.lang.String)

而且我注意到引用方法的索引始终为 30。最终代码可能看起来像

public Method unreference(Object methodRef) {
        ConstantPool constantPool = sun.misc.SharedSecrets.getJavaLangAccess().getConstantPool(methodRef.getClass());
        try {
            Object method = constantPool.getMethodAt(30);
            if (method instanceof Method) {
                return (Method) method;
            }
        }catch (Exception ignored) {
        }
        throw new IllegalArgumentException("Not a method reference.");
    }

在生产中小心这段代码!

于 2017-11-20T18:08:49.637 回答
-4

尝试这个

Thread.currentThread().getStackTrace()[2].getMethodName();
于 2017-07-26T14:55:13.053 回答