If you have an anonymous class like
Predicate<String> isEmpty = new Predicate<String>() {
public boolean test(String t) {
return t.isEmpty();
}
};
A library which is passed the reference to isEmpty
can inspect the byte code to see what it does and possibly manipulate it. Is there a way you can do this for lambdas?
Predicate<String> isEmpty = String::isEmpty;
e.g Say have this code and byte code
public class Main {
public static void test(Predicate<String> tester) {
System.out.println("tester.getClass()= " + tester.getClass());
System.out.println("tester.getClass().getClassLoader()="+ tester.getClass().getClassLoader());
}
public static void main(String... args) {
Predicate<String> isEmpty = String::isEmpty;
test(isEmpty);
}
}
$ javap -cp . -c -private Main.class
Compiled from "Main.java"
public class Main {
public Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void test(java.util.function.Predicate<java.lang.String>);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: ldc #5 // String tester.getClass()=
12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: aload_0
16: invokevirtual #7 // Method java/lang/Object.getClass:()Ljava/lang/Class;
19: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
22: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
25: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
28: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
31: new #3 // class java/lang/StringBuilder
34: dup
35: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
38: ldc #11 // String tester.getClass().getClassLoader()=
40: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
43: aload_0
44: invokevirtual #7 // Method java/lang/Object.getClass:()Ljava/lang/Class;
47: invokevirtual #12 // Method java/lang/Class.getClassLoader:()Ljava/lang/ClassLoader;
50: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
53: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
56: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
59: return
public static void main(java.lang.String...);
Code:
0: invokedynamic #13, 0 // InvokeDynamic #0:test:()Ljava/util/function/Predicate;
5: astore_1
6: aload_1
7: invokestatic #14 // Method test:(Ljava/util/function/Predicate;)V
10: return
}
With a reference to tester
in test
how do I find which method is called?
If you just want to SEE the bytecode:
But if you want to try to do this at runtime, you're pretty much out of luck (by design, we don't have anything like Expression Trees), as the other answer suggests.
After all, I found a way of getting hold of a lambda expression's class file that is rather reliable (but of course still relies on implementation details.) For an experimental implementation, I am now using a Java agent which allows for the retransformation of classes but which is itself implemented as a no-op, I am only interested in the binary array that is handed as an argument.
After getting hold of an
Instrumentation
instance via the registered Java agent, one registers aClassFileTransformer
which is then notified over the binary representation once one retransformslambdaInstance.getClass()
. This is of course a rather hacky solution and might break once the internal implementation of lambdas is changed in the future.Unfortunately, I did not find any documentation on how an agent is supposed to behave with class retransformation of a lambda expression's synthetic class on a standards-compatible JVM.
I found another way: by patching the
InnerClassLambdaMetafactory
it is possible to add an annotation to the lambda classes which points to the implementation method. This is possible at runtime by loading an agent and retransforming the metafactory. See my Blog for details.The simple answer is: You can't. There is a related answer of Brian Goetz on this matter. For implementing lambda expressions, javac creates an
INVOKEDYNAMIC
instruction which delegates the invocation to theLambdaMetafactory
's bootstrap method. For the OpenJDK, this bootstrap method is then creating an implementation of the required interface at runtime using ASM.Within the
test
method, the retreived instancetester
is of this ASM-generated class such that you have no class file to read for finding out which method thePredicate
represents. In the general case, the exact decision of how to map lambda expressions to interface implementations is left to the runtime environment what makes your problem even harder. The only way to find out which method a lambda expression represents is to read the byte code of its creation, i.e. interpreting themain
method, for your example.