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

Posted

技术标签:

【中文标题】如何获取 Java 8 方法参考的 MethodInfo?【英文标题】:How to get the MethodInfo of a Java 8 method reference? 【发布时间】:2013-11-19 15:29:54 【问题描述】:

请看下面的代码:

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

【问题讨论】:

看起来它毕竟是可能的,通过使用代理来记录哪个方法被调用。 ***.com/a/22745127/3478229 这是可能的,但这对代理类施加了限制。例如,它不能是最终的并且需要具有默认的公共或受保护的构造函数。此外,这不适用于 final 方法。 接受警告(我们无法区分 lambda,并且 lambda 可以包含欺骗我们的 IF),这是一种很好的方法,并且在 API 中很有用——如果你可以获取现成的 impl github.com/joj-io/joj-reflect/blob/master/src/main/java/io/joj/…希望。 我也一直想这样做。在大多数情况下,“正确”的做法是创建一个自定义注释并通过注释标记该方法,但这很快就会变得很麻烦。 How to get Method object in Java without using method string names 的可能重复项。它没有提到方法参考,但有几个合理的答案,不像这里的这个问题。 【参考方案1】:

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

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

此处的其他答案和 cmets 表明,目前可能通过一些额外的工作来检索绑定的方法,但请确保您了解注意事项。

【讨论】:

我很好奇是否有任何关于此限制的官方对话。在我看来,拥有类型安全的方法引用在 Java 中是非常有价值的。这似乎是添加它们的最佳时机,如果失去(显着)改进 Java 元编程的机会,那将是一种耻辱。 这不是限制;这只是您在寻找不同的功能,方法文字。这是与方法引用不同的野兽。 您的答案似乎不正确。这是可能的,尽管并不简单。 benjiweber.co.uk/blog/2013/12/28/… @BrianGoetz +100 用于方法文字和属性文字。我相信您在 JPA 规范团队的同事会在内部感激不尽;) @Devabc 当然在某些情况下可能可能,但是没有支持的API相当于OP提供的C#示例。我认为这是我回答的合理依据,我坚持这一点。其他可能性,例如使用代理,甚至分析字节码中的引导方法参数,要么不可靠,要么施加额外的限制。他们值得在单独的答案中提及吗?当然。他们是否让我的回答不正确或值得一票否决?我不明白怎么做。也就是说,我确实改写了我的答案,听起来不那么绝对。【参考方案2】:

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

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

如您所见,有人正在测试方法 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();

不,我可以简单地使用

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

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

缺点:

会比上面稍慢 需要 Mockito 才能工作 除了上面的用例之外几乎没有用处。

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

【讨论】:

谢谢,顺便说一句,我们可以强制开发人员自己扩展 bean 类(并在他们的模拟中实现 String getMethod()),而不是 Mockito。 不错的解决方案!顺便说一句,如果你在getMethodName 方法的签名中写Class&lt;T&gt; 而不是Class&lt;?&gt;,那么就不再需要将Mockito.mock() 转换为(T) 也不需要@SuppressWarnings("unchecked") @fbastien 不幸的是,由于泛型,object.getClass() 的类型不是Class&lt;? extends T&gt;,而只是Class&lt;?&gt;。例如,考虑TList&lt;String&gt; 的情况,getClass() 可以返回例如ArrayList.class,它不是List&lt;String&gt; 的子类型。另请参阅Object.getClass()’s javadoc 中有关实际返回类型的注释。【参考方案3】:

虽然我自己没有尝试过,但我认为答案是“不”,因为a method reference is semantically the same as a lambda.

【讨论】:

【参考方案4】:

您可以将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));

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

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

【讨论】:

使用 java 8 来实现这一点并非易事,而且需要比 Github 上的 README 指示更多的努力。它需要我付出更多的努力,因为我将它合并到一个大型复合/多项目 Gradle 构建中并将其加载到 Eclipse 中。我最终分叉了安全镜像并使其成为源依赖项。需要做的事情的基本概要: 1) jOOR 是用 Java 9 编译的;您必须将依赖项工件 ID 从 joor 更改为 joor-java-8。 2) 确保将 lombok 指定为编译范围的主要 测试依赖项。有用!!!谢谢@Hervian!【参考方案5】:

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

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

【讨论】:

【参考方案6】:

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

【讨论】:

【参考方案7】:

我们发布了小库reflection-util,可以用来捕获方法名。

例子:

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"

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

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

它不适用于 静态 方法。 如果类是final,则它不起作用。 我们目前不支持所有潜在的方法签名。它应该适用于不带参数的方法,例如 getter 方法。

【讨论】:

非常有限,不是通用的解决方案 而且它还依赖于 Unsafe,不能在现代 JDK 上使用 @rumatoest:反射实用程序支持现代 JDK。您能否在 GitHub 上针对不适合您的用例提出问题? 这仅适用于没有参数也没有返回类型的方法,对大多数情况无用... @YSavanier:在反射实用程序 2.9.0 中,我们引入了 ClassUtils.getMethodName(…),它允许您捕获具有返回类型的方法。我已经相应地更新了我的答案。但是,是的,实施肯定不是灵丹妙药,而且某些情况(尚不)受支持。无论如何,对于 some 人来说,为 some 案例提供解决方案可能会很有趣……【参考方案8】:

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

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

【讨论】:

我已经检查过了。它不适用于最终方法、最终类或静态方法。 @Rafal 是的,是的。我在文档中写过。但是您的否决票没有意义。首先,你的问题没有那个限制。我将在这里为您提供仅针对您的问题的解决方案。其次,我检查了上面的 4 个答案,谁给出了解决方案并且有超过 1 个支持。其中 3 个不能用于最终/静态方法。第三,反映静态方法很少见,为什么需要它? 我可以更改否决票,但您必须编辑您的答案才能让我这样做(由于 Stackoveflow 限制)。对其他问题的支持不是由我提出的。我的问题没有限制......是的......所以我希望答案涵盖所有情况,或者至少很清楚哪些不包括在内。 @Rafal 谢谢你,先生。没关系。如果您在其他地方看到我,请不要粗心投反对票:) 此处缺少关键信息,即您的库的如何工作。如果 Github 出现故障,那么这个答案就没用了。其他答案至少链接到 *** 上的一些相关代码。【参考方案9】:

所以,我玩这个代码

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

在生产中小心使用此代码!

【讨论】:

哦...是的,这对生产有风险;-) 虽然这是一种有趣的方法,但由于 sun.* 导入(因此,我猜是反对票),它只能在 Java 8 中工作。您可能需要更新它以使用最新版本的 Java 并描述限制。【参考方案10】:

试试这个

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

【讨论】:

请在您的回答中添加一些解释性文字。此外,OP 不想要当前调用的方法的名称,而是转发引用的方法的名称。

以上是关于如何获取 Java 8 方法参考的 MethodInfo?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Java 8 中的 LocalDateTime 获取毫秒数

如何从 Java 8 Streams 中获取 Inputstream?

Java取当前时间

myeclipse 8.5如何执行java项目

java如何把一个以文本存储的文件转化为二进制文件

08.Java反射问题