13

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.

Miguel Gamboa
  • 8,855
  • 7
  • 47
  • 94
  • 3
    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… – Holger Nov 16 '17 at 09:19

2 Answers2

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.

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 1
    Interesting! .. *might find clever things to do with these names* like? – Naman Nov 15 '17 at 17:19
  • 1
    @nullpointer: there are already existing Q&As on Stackoverflow regarding clever ways to find this information. – Holger Nov 15 '17 at 17:30
  • @Holger I sure hoped there is a duplicate... thank u for closing – Eugene Nov 15 '17 at 17:38
  • 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 @Holger 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](https://stackoverflow.com/q/34925524/1140754). 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. – Miguel Gamboa Nov 15 '17 at 18:26
  • 2
    @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)... – Holger Nov 15 '17 at 20:04
  • 1
    and I'm excitedly waiting which new solutions will show up after reopening… – Holger Nov 15 '17 at 20:08
  • @Holger 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. – Eugene Nov 15 '17 at 20:11
  • 2
    @Eugene: [this comment](https://stackoverflow.com/questions/19845213/how-to-get-the-methodinfo-of-a-java-8-method-reference#comment36054089_19845393) 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](https://stackoverflow.com/a/47312410/2711488) also illustrates the issue, you would get three different answers for semantically identical expressions… – Holger Nov 15 '17 at 20:20
0

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();
    }
Andy
  • 1
  • 2