3

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)

fps
  • 33,623
  • 8
  • 55
  • 110
John
  • 346
  • 1
  • 11
  • Although I haven't tried it with lambdas, you could use `Thread.currentThread().getStackTrace()` to get the current stack trace. The Elements returned offer a method `getMethodName()` that might print the right name. See [this question](https://stackoverflow.com/questions/1069066/get-current-stack-trace-in-java). – tom1299 Nov 20 '18 at 06:55

1 Answers1

5

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

fps
  • 33,623
  • 8
  • 55
  • 110
  • hmmm, i really like this answer, but i get the exception: java.lang.NoSuchMethodException: transaction.SaleBuilder$TransactionBuilder$$Lambda$288/0x000000010040f040.writeReplace() – John Nov 21 '18 at 00:00
  • if i can get this to work, this is exactly what i am after – John Nov 21 '18 at 00:01
  • WAIT, i havent made it serializable – John Nov 21 '18 at 00:04
  • 1
    YES, it works! thankyou, you are amazing cheers – John Nov 21 '18 at 00:05
  • @John Be aware of the performance penalty, as well as security risks that come from making your lambdas Serializable. – fps Nov 21 '18 at 00:47
  • 1
    Well noted, though I merely need this for debugging, cheers. – John Nov 21 '18 at 00:58