Can you inspect the byte code of a Java 8 lambda a

2019-03-09 01:51发布

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?

4条回答
Juvenile、少年°
2楼-- · 2019-03-09 02:36

If you just want to SEE the bytecode:

javap -c -p -v classfile
      ^disassemble
         ^private methods
            ^verbose, including constant pool and bootstrap methods attribute

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.

查看更多
Evening l夕情丶
3楼-- · 2019-03-09 02:37

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 a ClassFileTransformer which is then notified over the binary representation once one retransforms lambdaInstance.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
4楼-- · 2019-03-09 02:45

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.

查看更多
迷人小祖宗
5楼-- · 2019-03-09 02:47

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 the LambdaMetafactory'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 instance tester is of this ASM-generated class such that you have no class file to read for finding out which method the Predicate 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 the main method, for your example.

查看更多
登录 后发表回答