100

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);
    }
Rafal
  • 1,498
  • 5
  • 15
  • 20
  • I keep wanting to do this as well. The "Correct" way to do it in most cases would be to create a custom annotation and tag that method via the annotation, but that gets cumbersome pretty fast. – Bill K Jun 20 '18 at 22:09
  • Possible duplicate of [How to get Method object in Java without using method string names](https://stackoverflow.com/questions/9864300/how-to-get-method-object-in-java-without-using-method-string-names). It doesn't mention method reference but has several reasonable answers, unlike this question here. – Vadzim May 23 '19 at 21:10
  • Looks like it might be possible after all, by using a proxy to record which method gets called. https://stackoverflow.com/a/22745127/3478229 – ddan Mar 30 '14 at 14:29
  • It is possible, but this puts constraints on proxied class. For example, it cannot be final and need to have default public or protected constructor. Moreover this will not work for final methods. – Rafal Apr 01 '14 at 06:13
  • Accepting caveats (we can't distinguish lambdas, and a lambda can contain IFs fooling us), this is a good approach and useful in APIs -- you can grab a ready impl https://github.com/joj-io/joj-reflect/blob/master/src/main/java/io/joj/reflect/MethodReferences.java if you wish. – Piotr Findeisen Oct 29 '16 at 19:15

10 Answers10

41

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.

Mike Strobel
  • 25,075
  • 57
  • 69
  • 34
    I am curious if there is any official conversation about this limitation. It seems to me that having type-safe method references would be incredibly valuable in Java. This seems like the perfect time to add them and it would be a shame to see this opportunity to (significantly) improve java's meta-programming be lost. – Yona Appletree Jan 20 '14 at 22:32
  • 17
    This is not a limitation; it is simply you are looking for a different feature, _method literals_. This is a different beast from _method references_. – Brian Goetz May 07 '14 at 03:07
  • 1
    In your example, `MyClass::myMethod` is just a syntactic sugar of `new Action() { @Override public void invoke() { MyClass.myMethod(); } }`. It is unfortunate that Java wasn't made to have "real" method references, like in C#, as to maintain some VM / binary compatibility. If it wasn't a concern, I think they'd make `java.lang.reflect.Method` generic, so it behaves more like `java.lang.Class` and so `java.util.function.Function` would be unnecessary. But then, "variadic generics" (varargs in generic parameters) must also be introduced, like in C++ 11, to include all possible method signatures. – Siu Ching Pong -Asuka Kenji- Jan 01 '15 at 05:10
  • Then, `MyClass::myMethod` would have been of the type `Method` or alike, and it could be used like `java.util.function.Function` as well as for obtaining meta information. But, unfortunately, they did not make it this way... – Siu Ching Pong -Asuka Kenji- Jan 01 '15 at 05:15
  • 4
    It looks like your answer is incorrect. It's possible, though not straightforward. http://benjiweber.co.uk/blog/2013/12/28/typesafe-database-interaction-with-java-8/ – kd8azz Apr 25 '15 at 20:04
  • 3
    @BrianGoetz +100 for method literals and while at it property literals. I'm sure your colleagues at the JPA spec team would be internally thankful ;) – dexter meyers Jun 22 '15 at 09:58
  • No, as some already said, it is possible. See http://stackoverflow.com/questions/31178103/how-can-i-find-the-target-of-a-java8-method-reference and http://benjiweber.co.uk/blog/2013/12/28/typesafe-database-interaction-with-java-8/ – Devabc Jan 20 '16 at 13:57
  • 2
    @Devabc Of course it may be *possible* in some situations, but there is no supported API that is equivalent to the C# example provided by the OP. I think that is reasonable basis for my answer, and I stand by it. Other possibilities, like using proxies or even analyzing the bootstrap method arguments in the bytecode, are either unreliable or impose additional restrictions. Are they worth mentioning in a separate answer? Sure. Do they make my answer *incorrect* or worthy of a downvote? I do not see how. That said, I did rephrase my answer to sound less absolute. – Mike Strobel Jan 20 '16 at 18:01
  • 1
    If you don't mind using ugly hacks there is a way. Right now lambdas are generated using ASM library - so they are represented as regular JVM/Java classes. You must inspect generated bytecode to find you which method will be called and read value of private field to find out on which object. You may also access lambda closure this way. There is already library on GitHub that does exactly that but I cannot recall it's name right now... – csharpfolk Oct 21 '17 at 18:29
  • To mitigate the unreliability argument, I personaly found the cglib dummy proxification to get the otherwise erased type (like in https://stackoverflow.com/questions/19845213/how-to-get-the-methodinfo-of-a-java-8-method-reference/49998151#49998151 answer) to be the more elegant and simple way to do this as cglib is commonly used in production environments. Bytebuddy might be a reasonnable approach to as it is getting more and more leverage today. – YSavanier Feb 15 '21 at 09:18
14

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.

yankee
  • 38,872
  • 15
  • 103
  • 162
  • Thanks, BTW, instead of Mockito we can oblige the developers to extend bean classes themselves (and implement `String getMethod()` in their mocks) – basin Aug 30 '18 at 08:54
  • Nice solution! BTW, if you write `Class` instead of `Class>` in the signature of your `getMethodName` method, then there's no more need for casting `Mockito.mock()` to `(T)` nor having `@SuppressWarnings("unchecked")` – fbastien Jan 05 '22 at 07:59
  • @fbastien unfortunately that wouldn’t work because the type of `object.getClass()` is not `Class extends T>` but simply `Class>` because of the generics. Consider for example the case of `T` being `List`, `getClass()` could return e.g. `ArrayList.class`, which is not a subtype of `List`. See also the note on the actual return type in [Object.getClass()’s javadoc](https://cr.openjdk.java.net/~iris/se/11/latestSpec/api/java.base/java/lang/Object.html#getClass()). – Didier L Feb 07 '22 at 22:56
7

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

Matt Ball
  • 354,903
  • 100
  • 647
  • 710
5

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.

vivimice
  • 496
  • 8
  • 9
3

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).

Community
  • 1
  • 1
user102008
  • 30,736
  • 10
  • 83
  • 104
2

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.
Benedikt Waldvogel
  • 12,406
  • 8
  • 49
  • 61
  • Is very limited, not a general solution – xiaohuo May 23 '18 at 09:58
  • And also it depends on Unsafe and can not be used on modern JDKs – rumatoest Nov 11 '19 at 07:47
  • 1
    @rumatoest: reflection-util supports modern JDKs. Could you file an issue on GitHub for the use-case that doesn’t work for you? – Benedikt Waldvogel Nov 11 '19 at 17:46
  • This only works for methods without an argument nor a return type, useless for most cases... – YSavanier Feb 15 '21 at 09:05
  • @YSavanier: In reflection-util 2.9.0 we introduced ClassUtils.getMethodName(…) which allows you to capture methods with a return type. I’ve updated my answer accordingly. But yes, the implementation is certainly no silver bullet and some cases are not (yet) supported. Anyway, having a solution for *some* cases could be interesting for *some* people… – Benedikt Waldvogel Feb 17 '21 at 17:52
0

You can use my library Reflect Without String

Method myMethod = ReflectWithoutString.methodGetter(MyClass.class).getMethod(MyClass::myMethod);
Dean Xu
  • 4,438
  • 1
  • 17
  • 44
  • 2
    I've checked. It doesn't work for final methods, nor final classes nor static methods. – Rafal Apr 26 '18 at 05:03
  • 1
    @Rafal Yes, it is. I had written in the doc. But Your downvote does not make sense . First, your question has no that restriction. I'm going here to give you a solution only for your question. Second, I checked 4 answers above who gives solution and have more than 1 upvote. 3 of them can't work for final/static methods. Third, it's rare to reflect a static method, why you need that? – Dean Xu Apr 26 '18 at 05:23
  • I can change downvote, but you have to edit you answer to let me do that (due to Stackoveflow restriction). Upvotes for other questions has been made not by me. My question has no restrictions... yes... so I expect answer which covers all cases or at least it is clear which are not covered. – Rafal Apr 27 '18 at 06:30
  • @Rafal Thank you, sir. It doesn't matter. If you see me somewhere else, please don't downvote carelessly :) – Dean Xu Apr 27 '18 at 06:34
  • 3
    The crucial piece of info is missing here, i.e. *how* your library works. If Github goes down then this answer is useless. The other answers at least link to some relevant code on StackOverflow. – jmiserez Jan 28 '19 at 16:34
  • Thank you so much for you piece of code, this is the only simple way I found to do this java's lifehack. Such great idea of using cglib as a temporary proxification in order to get the otherwise erased genericity you are my hero ! :D – YSavanier Feb 15 '21 at 08:58
0

Another solution using Mockito:

pom.xml:

<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-core</artifactId>
  <scope>compile</scope>
</dependency>

Test code:

@Test
void testMethodNameGetter() {
    final var invocations = MethodUtil.getMethodFromGetter(DummyClass.class, DummyClass::getTestString);
    Assertions.assertEquals("getTestString", invocations.get(0).getMethod().getName());
}
@Test
void testMethodNameSetter() {
    final var invocations = MethodUtil.getMethodFromSetter(DummyClass.class, DummyClass::setTestString);
    Assertions.assertEquals("setTestString", invocations.get(0).getMethod().getName());
}

java code:

public class MethodUtil {

@Getter
@AllArgsConstructor
public static class MethodInvocation {
    private Method method;
    private Object[] arguments;
}

private MethodUtil() {
    // static class
}

public static <T> List<MethodInvocation> getMethodFromGetter(final Class<T> clazz, final Function<T, ?> getter) {
    return captureMethodOnInvocation(clazz, getter::apply);
}

public static <
        T,
        V> List<MethodInvocation> getMethodFromSetter(final Class<T> clazz, final BiConsumer<T, V> setter) {
    return captureMethodOnInvocation(clazz, (T mock) -> {

        final BiConsumer setterGeneric = setter;
        tryOneByOneUntilSuccess(
                () -> setterGeneric.accept(mock, 0),
                () -> setterGeneric.accept(mock, false),
                () -> setterGeneric.accept(mock, 0D),
                () -> setterGeneric.accept(mock, 0F),
                () -> setterGeneric.accept(mock, null));
    });
}

private static void tryOneByOneUntilSuccess(final Runnable... runnableArray) {
    for (int i = 0, runnableArrayLength = runnableArray.length; i < runnableArrayLength; i++) {
        final Runnable runnable = runnableArray[i];
        try {
            runnable.run();
            break;
        } catch (final NullPointerException | ClassCastException e) {
            if (i == runnableArrayLength - 1) {
                throw e;
            }
        }
    }

}

private static <
        T> List<MethodInvocation> captureMethodOnInvocation(final Class<T> clazz, final Consumer<T> invokeMock) {
    try {
        final List<MethodInvocation> methodReferences = new ArrayList<>();
        final InvocationListener invocationListener = new InvocationListener() {
            @Override
            public void reportInvocation(final MethodInvocationReport methodInvocationReport) {
                final InvocationOnMock invocation = (InvocationOnMock) methodInvocationReport.getInvocation();

                final Method method = invocation.getMethod();
                final Object[] arguments = invocation.getArguments();
                methodReferences.add(new MethodInvocation(method, arguments));
            }
        };
        final MockSettings mockSettings = Mockito.withSettings().invocationListeners(invocationListener);
        final T mock = Mockito.mock(clazz, mockSettings);
        invokeMock.accept(mock);
        return methodReferences;

    } catch (final Exception e) {
        throw new RuntimeException("Method could not be captured at runtime.", e);
    }
}

}

Jonas_Hess
  • 1,874
  • 1
  • 22
  • 32
-1

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

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!

Dmitriy V.
  • 47
  • 1
  • 4
  • Oh... yes, it is to risky for production ;-) – Rafal Apr 26 '18 at 05:04
  • While this is an interesting approach, it will only work in Java 8 due to the `sun.*` imports (hence, I guess, the downvotes). You may want to update it for more recent versions of Java and describe the limitations. – Didier L Feb 07 '22 at 23:26
-5

Try this

Thread.currentThread().getStackTrace()[2].getMethodName();
  • 4
    Please add some words of explanation to your answer. Furthermore, the OP does not want the name of a method currently called but of a method a reference to which is forwarded. – mkl Jul 26 '17 at 15:18