Is it possible (how) to get the name of a method r

2019-07-10 01:03发布

问题:

This question already has an answer here:

  • Printing debug info on errors with java 8 lambda expressions 1 answer
  • Reflection type inference on Java 8 Lambdas 5 answers
  • How to get the MethodInfo of a Java 8 method reference? 10 answers

I've been using a lot of method references and lambdas recently, and wanted to know at runtime if i could print to screen the source of the lambda ie its name, simply for debugging reasons. I figured it might be possible using reflection, by calling getClass() within getName(), but I couldn't find a method with which to find the original source reference's name.

I have a functional interface such as:

@FunctionalInterface
public interface FooInterface {
    // function etc etc irrelevant
    public void method();

    public default String getName() {
        // returns the name of the method reference which this is used to define
    }
}

then lets say i wish to test run the interface, and print the source of the functional interface to the screen.

public static void doStuff(FooInterface f) {
    // prints the lambda name that is used to create f
    System.out.println(f.getName());

    // runs the method itself
    f.method();
}

So that if i do this:

doStuff(Foo::aMethodReference);

it should print something like: "aMethodReference" to the screen, that way i can know, at runtime which methods are being run, in what order etc.

I'm quite doubtful that this is possible, considering that lambdas are not-quite-objects, but hey, i figured there could be a workaround. Furthermore, the eclipse debug tool just says its a lambda, without any other information, do lambda's retain any of this information? or is it all lost at Runtime?

Cheers. (I'm using JDK 11 if that makes any difference)

回答1:

As you're saying that you only need this for debugging purposes, here is a trick (i.e. a dirty hack) that will allow you to do what you want.

First of all, your functional interface must be Serializable:

@FunctionalInterface
public interface FooInterface extends Serializable {

    void method();
}

Now, you can use this undocumented, internal-implementation-dependent and extremely risky code to print some information about the method reference targeted to your FooInterface functional interface:

@FunctionalInterface
public interface FooInterface extends Serializable {

    void method();

    default String getName() {
        try {
            Method writeReplace = this.getClass().getDeclaredMethod("writeReplace");
            writeReplace.setAccessible(true);
            SerializedLambda sl = (SerializedLambda) writeReplace.invoke(this);
            return sl.getImplClass() + "::" + sl.getImplMethodName();
        } catch (Exception e) {
            return null;
        }
    }
}

When you call this method:

doStuff(Foo::aMethodReference);

You'll see the following output:

package/to/the/class/Foo::aMethodReference

Note 1: I've seen this approach in this article by Peter Lawrey.

Note 2: I've tested this with openjdk version "11" 2018-09-25 and also with java version "1.8.0_192".