How to get MethodInfo links to Java 8 method?

Please review the following code:

Method methodInfo = MyClass.class.getMethod("myMethod"); 

This works, but the method name is passed as a string, so it will be compiled even if myMethod does not exist.

Java 8, on the other hand, represents a function reference function. It is checked at compile time. Can I use this function to get information about a method?

 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 code that is equivalent to the following C # code:

  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); } 
+49
java reflection lambda java-8
Nov 07 '13 at 19:46
source share
10 answers

No, there is no reliable, supported way to do this. You assign a method reference to an instance of the functional interface, but this instance is created by LambdaMetaFactory , and there is no way to drill through it to find the method with which you were originally bound.

Lambdas and method references in Java work very differently than delegates in C #. For some interesting background read invokedynamic .

The other answers and comments here show that it is currently possible to get a related method with some extra work, but make sure you understand the caveats.

+20
Nov 07 '13 at 19:55
source share

This seems to be possible in the end, using a proxy to record which method is being called.

stack overflow

+12
Mar 30 '14 at 14:29
source share

Although I have not tried it myself, I think the answer is no, because the method reference is semantically the same as the lambda.

+5
Nov 07 '13 at 19:51
source share

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 the getAPoint method and checking that the coordinates are as expected, but in the description of each statement it was copied and not synchronized with what was marked. It would be better to write this only once.

From @ddan's ideas, I built a proxy solution using 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 just use

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

and it is guaranteed that the statement description will be synchronized with the code.

Downside:

  • Will be a little slower than above
  • Mockito is required to work.
  • It is unlikely that anything other than usecase above is useful.

However, it shows a way to do this.

+5
Jun 04 '16 at 14:05
source share

If you can make the Action extend Serializable interface, then this answer from another question seems to provide a solution (at least on some compilers and at runtime).

+3
Apr 18 '15 at 22:14
source share

We published a small de.cronn library : reflection-util , which can be used to capture the method name.

Example:

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

Implementation Details: A subclass of the MyClass routine is created using ByteBuddy , and a method call is captured to retrieve its name. ClassUtils caches information in such a way that we do not need to create a new proxy server with every call.

Note that this approach does not work with static methods.

+1
Nov 15 '17 at 8:40
source share

There may not be a reliable way, but in some circumstances:

  • your MyClass not final and has a constructor available (cglib restriction)
  • your myMethod not overloaded, not static

You can try using cglib to create a MyClass proxy and then use MethodInterceptor to tell Method , while the method reference is called in the next test run.

Code example:

 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, MethodRefWith1Arg is just syntactic sugar so you can reference a non-static method with one argument. You can create integer MethodRefWithXArgs to reference other methods.

0
Nov 17 '17 at 5:38 on
source share

try it

 Thread.currentThread().getStackTrace()[2].getMethodName(); 
-one
Jul 26 '17 at 14:55
source share

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:

 index = 30, method = public void Main.test(java.lang.String) 

And as I noticed, the index of the reference method is always 30. The final code may look like

 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!

-one
Nov 20 '17 at 18:08
source share

You can use my library Reflect Without String

 Method myMethod = ReflectWithoutString.methodGetter(MyClass.class).getMethod(MyClass::myMethod); 
-one
Apr 24 '18 at 9:33
source share



All Articles