如何获取 Java 8 方法引用的 MethodInfo?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19845213/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to get the MethodInfo of a Java 8 method reference?
提问by Rafal
Please have a look at the following code:
请看下面的代码:
Method methodInfo = MyClass.class.getMethod("myMethod");
This works, but the method name is passed as a string, so this will compile even if myMethod does not exist.
这有效,但方法名称作为字符串传递,因此即使 myMethod 不存在也会编译。
On the other hand, Java 8 introduces a method reference feature. It is checked at compile time. It is possible to use this feature to get method info?
另一方面,Java 8 引入了方法引用功能。它在编译时检查。是否可以使用此功能来获取方法信息?
printMethodName(MyClass::myMethod);
Full example:
完整示例:
@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);
}
In other words I would like to have a code which is the equivalent of the following C# code:
换句话说,我想要一个与以下 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);
}
采纳答案by Mike Strobel
No, there is no reliable, supported way to do this. You assign a method reference to an instance of a functional interface, but that instance is cooked up by LambdaMetaFactory, and there is no way to drill into it to find the method you originally bound to.
不,没有可靠的、受支持的方法来做到这一点。您将方法引用分配给函数式接口的实例,但该实例是由 制作的LambdaMetaFactory,并且无法深入其中以找到您最初绑定到的方法。
Lambdas and method references in Java work quite differently than delegates in C#. For some interesting background, read up on invokedynamic.
Java 中的 Lambda 和方法引用的工作方式与 C# 中的委托截然不同。有关一些有趣的背景,请继续阅读invokedynamic。
Other answers and comments here show that it may currently be possible to retrieve the bound method with some additional work, but make sure you understand the caveats.
此处的其他答案和评论表明,目前可以通过一些额外的工作来检索绑定方法,但请确保您了解注意事项。
回答by Matt Ball
Though I haven't tried it myself, I think the answer is "no," since a method reference is semantically the same as a lambda.
虽然我自己没有尝试过,但我认为答案是“不”,因为方法引用在语义上与 lambda 相同。
回答by user102008
If you can make the interface Actionextend Serializable, then this answerfrom another question seems to provide a solution (at least on some compilers and runtimes).
如果您可以使接口Action扩展Serializable,那么来自另一个问题的这个答案似乎提供了一个解决方案(至少在某些编译器和运行时)。
回答by yankee
In my case I was looking for a way to get rid of this in unit tests:
就我而言,我正在寻找一种在单元测试中摆脱这种情况的方法:
Point p = getAPoint();
assertEquals(p.getX(), 4, "x");
assertEquals(p.getY(), 6, "x");
As you can see someone is testing Method getAPointand checks that the coordinates are as expected, but in the description of each assert was copied and is not in sync with what is checked. Better would be to write this only once.
正如您所看到的,有人正在测试 MethodgetAPoint并检查坐标是否符合预期,但在每个断言的描述中都被复制并且与检查的内容不同步。最好只写一次。
From the ideas by @ddan I built a proxy solution using Mockito:
根据@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();
}
No I can simply use
不,我可以简单地使用
assertPropertyEqual(p, Point::getX, 4);
assertPropertyEqual(p, Point::getY, 6);
and the description of the assert is guaranteed to be in sync with the code.
并且断言的描述保证与代码同步。
Downside:
缺点:
- Will be slightly slower than above
- Needs Mockito to work
- Hardly useful to anything but the usecase above.
- 会比上面略慢
- 需要 Mockito 才能工作
- 除了上面的用例之外,几乎没有任何用处。
However it does show a way how it could be done.
然而,它确实显示了如何做到这一点。
回答by Asghar Nemati
Try this
尝试这个
Thread.currentThread().getStackTrace()[2].getMethodName();
回答by Benedikt Waldvogel
We have published the small library de.cronn:reflection-utilthat can be used to capture a method name.
我们发布了可用于捕获方法名称的小型库de.cronn:reflection-util。
Example:
例子:
class MyClass {
public void myMethod() {
}
}
String methodName = ClassUtils.getVoidMethodName(MyClass.class, MyClass::myMethod);
System.out.println(methodName); // prints "myMethod"
Implementation details: A Proxy subclass of MyClassis created with ByteBuddyand a call to the method is captured to retrieve its name.
ClassUtilscaches the information such that we do not need to create a new proxy on every invocation.
实现细节:MyClass使用ByteBuddy创建了一个 Proxy 子类,并捕获对该方法的调用以检索其名称。
ClassUtils缓存信息,这样我们就不需要在每次调用时都创建一个新的代理。
Please note that this approach doesn't work with staticmethods.
请注意,此方法不适用于静态方法。
回答by vivimice
There may not be a reliable way, but under some circumstances:
可能没有可靠的方法,但在某些情况下:
- your
MyClassis not final, and has an accessible constructor (limitation of cglib) - your
myMethodis not overloaded, and not static
- 您
MyClass不是最终的,并且具有可访问的构造函数(cglib 的限制) - 你
myMethod没有超载,也不是静态的
The you can try using cglib to create a proxy of MyClass, then using an MethodInterceptorto report the Methodwhile the method reference is invoked in a following trial run.
您可以尝试使用 cglib 创建 的代理MyClass,然后使用MethodInterceptor来报告Method在随后的试运行中调用方法引用时的情况。
Example code:
示例代码:
public static void main(String[] args) {
Method m = MethodReferenceUtils.getReferencedMethod(ArrayList.class, ArrayList::contains);
System.out.println(m);
}
You will see the following output:
您将看到以下输出:
public boolean java.util.ArrayList.contains(java.lang.Object)
While:
尽管:
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;
}
}
In the above code, MethodRefWith1Argis just a syntax sugar for you to reference an non-static method with one arguments. You can create as many as MethodRefWithXArgsfor referencing your other methods.
在上面的代码中,MethodRefWith1Arg只是一种语法糖,供您使用一个参数引用非静态方法。您可以创建与MethodRefWithXArgs引用其他方法一样多的内容。
回答by Dmitriy V.
So, I play with this code
所以,我玩这个代码
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();
}
}
output of this code is:
这段代码的输出是:
index = 30, method = public void Main.test(java.lang.String)
And as I notice index of referenced method is always 30. Final code may look like
我注意到引用方法的索引总是 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.");
}
Be careful with this code in production!
在生产中小心这段代码!
回答by Dean Xu
You can use my library Reflect Without String
你可以使用我的图书馆 Reflect Without String
Method myMethod = ReflectWithoutString.methodGetter(MyClass.class).getMethod(MyClass::myMethod);
回答by Hervian
You can add safety-mirrorto your classpath and do like this:
您可以将安全镜像添加到您的类路径并执行以下操作:
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
The library also offers a getName(...)method:
该库还提供了一种getName(...)方法:
assertEquals("isEmpty", Types.getName(String::isEmpty));
The library is based on Holger's answer: https://stackoverflow.com/a/21879031/6095334
该库基于 Holger 的回答:https: //stackoverflow.com/a/21879031/6095334
Edit: The library have various shortcomings which I am slowly becoming aware of. See fx Holger's comment here: How to get the name of the method resulting from a lambda
编辑:图书馆有我慢慢意识到的各种缺点。在此处查看 fx Holger 的评论:How to get the name of the method results from a lambda

