如何获取Java 8方法引用的MethodInfo?

Raf*_*fal 68 java reflection lambda java-8

请看下面的代码:

Method methodInfo = MyClass.class.getMethod("myMethod");
Run Code Online (Sandbox Code Playgroud)

这可行,但方法名称作为字符串传递,因此即使myMethod不存在,这也将编译.

另一方面,Java 8引入了方法引用功能.它在编译时检查.可以使用此功能获取方法信息吗?

printMethodName(MyClass::myMethod);
Run Code Online (Sandbox Code Playgroud)

完整示例:

@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);
}
Run Code Online (Sandbox Code Playgroud)

换句话说,我希望有一个代码,它相当于以下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);
    }
Run Code Online (Sandbox Code Playgroud)

Mik*_*bel 28

不,没有可靠的,受支持的方式来做到这一点.您将方法引用分配给功能接口的实例,但该实例是由实例生成的LambdaMetaFactory,并且无法深入查找该实例以查找最初绑定的方法.

Java中的Lambda和方法引用与C#中的委托完全不同.对于一些有趣的背景,请阅读invokedynamic.

这里的其他答案和评论表明,目前可能通过一些额外的工作来检索绑定方法,但请确保您了解警告.

  • 如果有关于此限制的任何官方对话,我很好奇.在我看来,在Java中使用类型安全的方法引用将是非常有价值的.这似乎是添加它们的最佳时机,看到这个(显着)改进java的元编程丢失的机会将是一种耻辱. (21认同)
  • 这不是限制; 它只是你正在寻找一个不同的功能,_方法文字_.这是与_method references_不同的野兽. (14认同)
  • 看起来你的答案是不正确的.这是可能的,虽然不是直截了当的.http://benjiweber.co.uk/blog/2013/12/28/typesafe-database-interaction-with-java-8/ (3认同)
  • @BrianGoetz +100用于方法文字,而在它的属性文字.我相信JPA规范团队的同事会内心感激;) (2认同)
  • @Devabc当然,在某些情况下它可能是**,但是没有支持的API等同于OP提供的C#示例.我认为这是我答案的合理依据,我坚持不懈.其他可能性,例如使用代理或甚至分析字节码中的引导方法参数,要么不可靠,要么施加额外的限制.他们在单独的答案中值得一提吗?当然.他们让我的答案*不正确*还是值得推特?我不知道怎么样.也就是说,我确实改写了我的回答,听起来不那么绝对. (2认同)

yan*_*kee 8

在我的情况下,我正在寻找一种方法来摆脱单元测试:

Point p = getAPoint();
assertEquals(p.getX(), 4, "x");
assertEquals(p.getY(), 6, "x");
Run Code Online (Sandbox Code Playgroud)

正如您所看到有人正在测试方法getAPoint并检查坐标是否符合预期,但是每个断言的描述都被复制并且与检查的内容不同步.更好的是只写一次.

根据@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();
}
Run Code Online (Sandbox Code Playgroud)

不,我可以简单地使用

assertPropertyEqual(p, Point::getX, 4);
assertPropertyEqual(p, Point::getY, 6);
Run Code Online (Sandbox Code Playgroud)

并且保证断言的描述与代码同步.

下行:

  • 会比上面稍慢
  • 需要Mockito工作
  • 除了上面的用例之外几乎没什么用处.

但它确实显示了如何完成它的方式.


Mat*_*all 6

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


Her*_*ian 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
Run Code Online (Sandbox Code Playgroud)

该库还提供了一种getName(...)方法:

assertEquals("isEmpty", Types.getName(String::isEmpty));
Run Code Online (Sandbox Code Playgroud)

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

编辑:图书馆有我慢慢意识到的各种缺点。在此处查看 fx Holger 的评论:How to get the name of the method results from a lambda


viv*_*ice 5

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

  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);
}
Run Code Online (Sandbox Code Playgroud)

您将看到以下输出:

public boolean java.util.ArrayList.contains(java.lang.Object)
Run Code Online (Sandbox Code Playgroud)

尽管:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

在上面的代码中,MethodRefWith1Arg只是一个语法糖,供您使用一个参数引用非静态方法。您可以创建尽可能多的值MethodRefWithXArgs以引用您的其他方法。