How to get the name of the method resulting from a lambda
Asked Answered
H

2

14

From a simple point of view, lambda expressions are compiled into static methods, since they don’t capture this (i.e. do not access instance members).

public class App {
  public static void foo(){
      Consumer<Integer> c1 = n -> {};
      Consumer<Integer> c2 = n -> {};
  }

  public static void main(String [] args) {
    Supplier<String> sup1 = () -> "I am sup1";
    Supplier<String> sup2 = () -> "I am sup2";
    Supplier<String> sup3 = () -> "I am sup3";

    Stream.of(App.class.getDeclaredMethods()).forEach(System.out::println);
  }
}

Running the previous example and checking the generated bytecodes with the javap tool, we can see that lambdas referred by variables c1, c2, sup1, sup2, sup3 are compiled to methods with names:

  • lambda$foo$0
  • lambda$foo$1
  • lambda$main$2
  • lambda$main$3
  • lambda$main$4

However, if we print the getClass() for each object referred by those variables (c1, c2, sup1, sup2, sup3) we will get, respectively:

  • class App$$Lambda$5/1705736037
  • class App$$Lambda$6/455659002
  • class App$$Lambda$1/791452441
  • class App$$Lambda$2/531885035
  • class App$$Lambda$3/1418481495

So, how can we make the correspondence between the runtime class names and the name of the methods resulting from the lambdas?

UPDATE

None of the solutions pointed in the following duplicated questions helps me to solve the problem that I am asking in my question:

The only way that I found was to adapt the solution of @Holger from its answer (not the accepted one) to the question Java 8: convert lambda to a Method instance with clousure included. However, in my case, instead of a Method object I am just looking for the name of the method. So, adapting that lambdaToMethod() to the following methodNameFromLambda() I got a solution:

static String methodNameFromLambda(Serializable lambda) {
  try {
    Method m = lambda.getClass().getDeclaredMethod("writeReplace");
    m.setAccessible(true);
    SerializedLambda sl=(SerializedLambda)m.invoke(lambda);
    return sl.getImplMethodName();
  } catch(ReflectiveOperationException ex) {
    throw new RuntimeException(ex);
  }
}

Now, lambdas of the example must be cast to Consumer<Integer>&Serializable or Supplier<String>&Serializable as describe in @Holger answer.

I am not aware if this solution is correct, but it works for my example and for the cases that I am working on.

Hint answered 15/11, 2017 at 17:15 Comment(1)
My original code looped through the lambda’s class hierarchy, because there is no guaranty that the writeReplace method is within the actual class. It would be a reasonable strategy to let lambdas inherit that method from a common base class instead of re-implementing it in every generated class, so I made the code ready to handle this scenario. To be pessimistic, there is no guaranty for the presence of this method at all, e.g. a different JRE could implement the replacement within the depths of the ObjectOutputStream implementation…Willem
I
4

This is subject to change from version to version - there is no specification that says what the exact names would be - and it's done on purpose; well to protect against code that might find clever things to do with these names.

Impedance answered 15/11, 2017 at 17:17 Comment(8)
Interesting! .. might find clever things to do with these names like?Turnaround
@nullpointer: there are already existing Q&As on Stackoverflow regarding clever ways to find this information.Willem
@Willem I sure hoped there is a duplicate... thank u for closingImpedance
None of the questions pointed as duplicated helped me to solve my problem. The only useful solution that I found close to my problem was the proposal of @Willem of using serializable lambdas in its answer (not the accepted one) to the question Java 8: convert lambda to a Method instance with clousure included. I updated my question with an adapted solution from that answer. I am not sure if it is correct, but it works for my example and for the cases that I am working on.Hint
@Miguel Gamboa: the bottom line of all linked Q&As is that there is no general solution at all, only partial solutions with specific drawbacks. If someone finds a better solution, it should be added to one of those existing questions. The purpose of the closing is to avoid having answers to the same thing spread over multiple questions (more than we already have)...Willem
and I'm excitedly waiting which new solutions will show up after reopening…Willem
@Willem i still don't get this to be quite honest. they wanted to do all possible actions to prohibit this, on purpose and yet there are people that would need it. If this is a feature that is requested - why not put it into Unsafe or SharedSecrets or whatever with a big fat label - do it on your own risk - we will change this as we please.Impedance
@Eugene: this comment nails it—it’s simply an entirely different feature. For a Supplier instance, there is no guaranty that it has been implemented as lambda/method reference at all nor that the creator is willing to allow this kind of reflection about something that should be an implementation detail. This answer also illustrates the issue, you would get three different answers for semantically identical expressions…Willem
C
2

may this help you. First, you should declare a interface which extends from Function and Serializable, and adding @FunctionalInterface annotation on it, like below:

@FunctionalInterface
public interface MyFunction<T, V> extends Function<T, V>, Serializable {
}

then, you can use your method to get the lambda method name.

 /**
     * get method name from lambda expression
     *
     * @param lambda
     * @return
     */
    private String methodNameFromLambda(Serializable lambda) {
        try {
            Method m = lambda.getClass().getDeclaredMethod("writeReplace");
            m.setAccessible(true);
            SerializedLambda sl = (SerializedLambda) m.invoke(lambda);
            return sl.getImplMethodName();
        } catch (ReflectiveOperationException ex) {
            throw new RuntimeException(ex);
        }
    }

here is a unit test demo

@Test
    public void main() {
        MyFunction<String, Integer> func = this::stringLength;
        String lambdaName = methodNameFromLambda(func);
        // output: stringLength
        System.out.println(lambdaName);
    }

stringLength function

private int stringLength(String s) {
        return s.length();
    }
Cart answered 23/8, 2023 at 6:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.