12

Is it possible to convert a method reference (e.g. SomeClass::someMethod) to a MethodHandle instance? I want the benefits of compile-time checking (ensuring that the class and method exists) as well as the ability to introspect the method using the MethodHandle API.

Use-case: I've got code that needs to execute if and only if the request was not triggered by a specific method (to avoid endless recursion). I want a compile-time check to ensure the class/method exists but a runtime check to compare the caller to the method.

So to recap: Is it possible to convert a method reference to a MethodHandle?

assylias
  • 321,522
  • 82
  • 660
  • 783
Gili
  • 86,244
  • 97
  • 390
  • 689
  • 2
    http://stackoverflow.com/questions/17143104/how-to-indirectly-run-a-method-reference-in-java-8 – assylias Oct 30 '14 at 09:40
  • 1
    No you can't; recommended viewing: [here](http://www.youtube.com/watch?v=C_QbkGU_lqY). In short: method references etc are dealt with using invokedynamic call sites. Such call sites _are_ programmable however, see [here](http://docs.oracle.com/javase/8/docs/api/java/lang/invoke/CallSite.html). – fge Oct 30 '14 at 09:48

1 Answers1

4

Well, if you can afford the additional overhead and security implications, you can use a Serializable functional interface and decode the serialized form of the method reference instance to find the target like demonstrated in this answer or brought up again with this question and its answers.

However, you should really rethink your software design. “Avoiding endless recursion” shouldn’t be fixed by decoding some kind of parameter object, especially not if your assumption is, that this actual argument value represents the caller of your method. How would you ever enforce this strange relationship?

Even a simple code change like referencing a method which delegates to the other method would break your check. Here is a simple example showing the subtle problems with your approach:

public class SimpleTest {
    public static void main(String... arg) {
        run(SimpleTest::process);
    }
    static void run(BiConsumer<Object,Object> c) {
        c.accept("foo", "bar");
    }
    static void process(Object... arg) {
        Thread.dumpStack();
    }
}

When running this program it will print something like:

java.lang.Exception: Stack trace
    at java.lang.Thread.dumpStack(Thread.java:1329)
    at SimpleTest.process(SimpleTest.java:16)
    at SimpleTest.lambda$MR$main$process$a9318f35$1(SimpleTest.java:10)
    at SimpleTest$$Lambda$1/26852690.accept(Unknown Source)
    at SimpleTest.run(SimpleTest.java:13)
    at SimpleTest.main(SimpleTest.java:10)

showing that the method reference within the generated instance is not the expected SimpleTest::process but instead SimpleTest::lambda$MR$main$process$a9318f35$1 which will eventually invoke process. The reason is that some operations (here varargs processing) are not performed by the generated interface instance but a synthetic method instead, just like you had written run((a,b)-> SimpleTest.process(a,b)). The only difference is the name of the synthetic method.

You shouldn’t design software relying on such fragile introspection. If you want to avoid recursion, a simple ThreadLocal flag telling whether you are already inside your specific method would do the job. But it might be worth asking yourself why your API is provoking endless recursion in the first place; there seems to be something fundamentally wrong…

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
  • I dumbed-down my use-case in order to simplify the question. I've got an HTTP server that maps request URLs to a handler class/method. I need to redirect all incoming calls that don't map to a specific handler (method). Instead of comparing against the URL (which will change over time) I want to compare against a value that will get picked up by code-refactoring tools and will provide compile-time safety. Is there another way for me to achieve my goal? That is, construct one method reference that is verified at compile-time and compare it against a second `java.lang.reflect.Method` at runtime? – Gili Oct 30 '14 at 19:47
  • 1
    Well, I assume that all handler implement the same `interface` which is also the functional interface you implement via method references. In that case, I’d recommend to implement the one specific handler which you need to recognize as an ordinary interface implementation which can have a well defined identity or equality. While the others, implemented via method references, don’t have a defined identity nor equality, they will never be equal to your specific ordinary interface implementation. – Holger Oct 31 '14 at 08:53
  • No actually, the methods aren't tagged by interface. I am using Jersey (JAX-RS) so in my case, methods are tagged by annotation (e.g. `@GET`, `@Path`, etc). I can't think of a way to bind against such methods with compile-time checks to make sure I'm not left with a dangling reference to a non-existent method. – Gili Oct 31 '14 at 12:57
  • 1
    It’s still not clear to me where the desired comparison between a `Method` and that compile-time checked artifact comes into play. But it has grown to an entirely different question anyway. So I suggest opening a new question with a more detailed explanation of what you want to achieve (maybe with code example). – Holger Oct 31 '14 at 13:24