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

时间:2013-11-07 19:46:19

标签: java reflection lambda java-8

请查看以下代码:

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

10 个答案:

答案 0 :(得分:26)

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

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

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

答案 1 :(得分:7)

就我而言,我一直在寻找一种方法来摆脱单元测试:

using (var db = new BloggingContext())
{
    var m = db.Blogs.FirstOrDefault(); // the first entry
    m.BlogImage = new BlogImage { Caption = "My Caption" }; // does not work
    db.BlogImage.Add(new BlogImage { Blog = m, BlogId = m.BlogId, Caption = "My Caption" }); // doesn't work either
    db.SaveChanges();                
}

正如您所看到有人正在测试方法Point p = getAPoint(); assertEquals(p.getX(), 4, "x"); assertEquals(p.getY(), 6, "x"); 并检查坐标是否符合预期,但是每个断言的描述都被复制并且与检查的内容不同步。最好只写一次。

根据@ddan的想法,我使用Mockito构建了一个代理解决方案:

getAPoint

不,我可以简单地使用

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

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

缺点:

  • 会比上面稍慢
  • 需要Mockito工作
  • 除了上面的用例外,几乎没用。

然而,它确实显示了如何完成它的方式。

答案 2 :(得分:5)

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

答案 3 :(得分:3)

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

答案 4 :(得分:2)

我们发布了可用于捕获方法名称的小型库de.cronn:reflection-util

示例:

class MyClass {

    public void myMethod() {
    }

}

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

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

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

答案 5 :(得分:2)

您可以将safety-mirror添加到您的类路径中,并执行以下操作:

 GetSchemaOptions.GetAssociationMemberName = key => "Association_" + key.MemberName; 

该库还提供了一种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(...)

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

编辑:该库具有各种缺点,我逐渐意识到这些缺点。 在此处查看fx Holger的评论:How to get the name of the method resulting from a lambda

答案 6 :(得分:1)

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

  1. 您的MyClass不是最终版,并且具有可访问的构造函数(cglib的限制)
  2. 您的myMethod未超载,而非静态
  3. 您可以尝试使用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来引用其他方法。

答案 7 :(得分:0)

您可以使用我的库Reflect Without String

 let formData = Object.assign({});
 formData = Object.assign(formData, this.bookFormGroup.value);
 this.newBook = new Book(formData.name ,formData.isbn );
 console.log(this.newBook instanceof Book);

答案 8 :(得分:-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.");
    }

请谨慎使用此代码!

答案 9 :(得分:-3)

试试这个

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