如何获取 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<T>
而不是Class<?>
,那么就不再需要将Mockito.mock()
转换为(T)
也不需要@SuppressWarnings("unchecked")
@fbastien 不幸的是,由于泛型,object.getClass()
的类型不是Class<? extends T>
,而只是Class<?>
。例如,考虑T
为List<String>
的情况,getClass()
可以返回例如ArrayList.class
,它不是List<String>
的子类型。另请参阅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 获取毫秒数