如何获取Java 8方法参考的MethodInfo?


76

请看下面的代码:

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

看起来通过使用代理来记录调用哪个方法毕竟是可能的。stackoverflow.com/a/22745127/3478229
ddan 2014年

可能,但是这对代理类施加了限制。例如,它不能是最终的,而需要具有默认的public或protected构造函数。此外,这对于最终方法将不起作用。
Rafal)2014年

接受警告(我们不能区分lambda,并且lambda可以包含欺骗我们的IF),这是一种很好的方法,并且在API中很有用-您可以获取现成的impl github.com/joj-io/joj-reflect/blob / master / src / main / java / io / joj / ...如果您愿意的话。
皮特·芬迪森

我也一直想这样做。在大多数情况下,“正确”的方法是创建一个自定义批注,并通过该批注标记该方法,但这很快就会变得麻烦。
比尔K

在不使用方法字符串名称的情况下如何获取Java中的Method对象的可能重复项。它没有提到方法参考,但是有几个合理的答案,这与这里的问题不同。
Vadzim

Answers:


30

不,没有可靠的,受支持的方式来执行此操作。您将方法引用分配给功能接口的实例,但是该实例是由编写的LambdaMetaFactory,无法深入到该实例中以查找最初绑定到的方法。

Java中的Lambda和方法引用的工作方式与C#中的委托完全不同。有关一些有趣的背景,请继续阅读invokedynamic

此处的其他答案和评论表明,通过其他一些工作,当前可能可以检索绑定方法,但是请确保您了解警告。


25
我很好奇是否有关于此限制的正式对话。在我看来,在Java中拥有类型安全的方法引用将非常有价值。这似乎是添加它们的最佳时机,而失去机会(显着)改善Java的元编程将是可耻的。
Yona Appletree,2014年

14
这不是限制;仅仅是您在寻找其他功能,方法文字。这与方法引用不同。
Brian Goetz 2014年

3
您的答案似乎不正确。有可能,尽管不是那么简单。 benjiweber.co.uk/blog/2013/12/28/…–
kd8azz

2
@BrianGoetz +100用于方法文字,同时使用它的属性文字。我敢肯定,您在JPA规范团队中的同事会对内部表示感谢;)
dexter meyers 2015年

2
@Devabc当然在某些情况下可能是可行的,但是没有与OP提供的C#示例等效的受支持API。我认为这是我回答的合理基础,我坚持。其他可能性,例如使用代理,甚至分析字节码中的引导方法参数,都不可靠或施加了其他限制。他们值得在另一个答案中提及吗?当然。他们使我的答案不正确或值得投票吗?我不知道如何。就是说,我确实改了一下我的回答,听起来不太绝对。
Mike Strobel

8

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

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工作
  • 除上述用例外,几乎对其他任何功能都没有用。

但是,它确实显示了一种方法。


谢谢你,顺便说一句,而不是中的Mockito,我们可以迫使开发商延长bean类本身(并实现String getMethod()他们的嘲笑)
盆地


3

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


3

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

  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只是一个语法糖,您可以使用一个参数引用一个非静态方法。您可以创建尽可能多MethodRefWithXArgs的引用其他方法。


3

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

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的方法的名称


1

我们已经发布了小的库reflect-util,可用于捕获方法名称。

例:

class MyClass {

    public void myMethod() {
    }

}

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

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

请注意,这种方法不适用于静态方法。


是非常有限的,不是通用解决方案
xiaohuo

而且它还取决于Unsafe,因此不能在现代JDK上使用
rumatoest

@rumatoest:Reflection-util支持现代的JDK。您可以针对不适合您的用例在GitHub上提交问题吗?
Benedikt Waldvogel,

-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.");
    }

在生产中请谨慎使用此代码!


哦...是的,这对生产有风险;-)
拉法尔

-1

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

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

1
我查过 它不适用于final方法,final类或静态方法。
拉法尔

1
@Rafal是的。我已经在文档中写过。但是你的反对票没有意义。首先,您的问题没有限制。我要在这里为您提供仅针对您的问题的解决方案。其次,我检查了上述4个答案,谁给出了解决方案并拥有1个以上的赞成票。其中3个不适用于最终/静态方法。第三,很难反映出静态方法,为什么需要它呢?
Dean Xu

我可以更改投票权,但是您必须编辑答案才能让我这样做(由于Stackoveflow限制)。我没有提出其他问题的投票。我的问题没有限制...是的...所以我希望得到涵盖所有情况的答案,或者至少可以清楚地看到未涵盖的情况。
拉法尔

@Rafal先生,谢谢。没关系 如果您在其他地方看到我,请不要粗心地投票:)
Dean Xu

1
此处缺少关键信息,即您的图书馆如何工作。如果Github失败了,那么这个答案就没用了。其他答案至少链接到StackOverflow上的一些相关代码。
jmiserez

-3

试试这个

Thread.currentThread().getStackTrace()[2].getMethodName();

3
请在回答中添加一些解释性的文字。此外,OP不需要当前调用的方法的名称,而是要转发其引用的方法的名称。
mkl
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.