Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get method references via reflection? [duplicate]

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.

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?

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:

    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);
    }
like image 260
Rafal Avatar asked Nov 07 '13 19:11

Rafal


7 Answers

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.

Lambdas and method references in Java work quite differently than delegates in C#. For some interesting background, read up on 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.

like image 186
Mike Strobel Avatar answered Oct 12 '22 23:10

Mike Strobel


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 getAPoint and 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.

From the ideas by @ddan 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 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.

However it does show a way how it could be done.

like image 37
yankee Avatar answered Oct 12 '22 23:10

yankee


Though I haven't tried it myself, I think the answer is "no," since a method reference is semantically the same as a lambda.

like image 41
Matt Ball Avatar answered Oct 13 '22 00:10

Matt Ball


You can add safety-mirror to 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:

assertEquals("isEmpty", Types.getName(String::isEmpty));

The library is based on Holger's answer: 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

like image 45
Hervian Avatar answered Oct 12 '22 23:10

Hervian


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

  1. your MyClass is not final, and has an accessible constructor (limitation of cglib)
  2. your myMethod is not overloaded, and not static

The you can try using cglib to create a proxy of MyClass, then using an MethodInterceptor to report the Method while the method reference is invoked in a following trial run.

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, MethodRefWith1Arg is just a syntax sugar for you to reference an non-static method with one arguments. You can create as many as MethodRefWithXArgs for referencing your other methods.

like image 32
vivimice Avatar answered Oct 13 '22 00:10

vivimice


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

like image 32
user102008 Avatar answered Oct 12 '22 23:10

user102008


We have published the small library reflection-util that can be used to capture a method name.

Example:

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"

Implementation details: A Proxy subclass of MyClass is created with ByteBuddy and a call to the method is captured to retrieve its name. ClassUtils caches the information such that we do not need to create a new proxy on every invocation.

Please note that this approach is no silver bullet and there are some known cases that don’t work:

  • It doesn’t work for static methods.
  • It doesn’t work if the class is final.
  • We currently do not support all potential method signatures. It should work for methods that do not take an argument such as a getter method.
like image 32
Benedikt Waldvogel Avatar answered Oct 13 '22 00:10

Benedikt Waldvogel