你能在运行时检查Java 8 lambda的字节码吗?

2022-09-01 23:35:14

如果你有一个匿名类,比如

Predicate<String> isEmpty = new Predicate<String>() {
    public boolean test(String t) {
        return t.isEmpty();
    }
};

传递引用的库可以检查字节码以查看其功能并可能对其进行操作。有没有办法为lambdas做到这一点?isEmpty

Predicate<String> isEmpty = String::isEmpty;

例如,假设有这个代码和字节代码

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        
}

与参考 如何找到调用哪个方法?testertest


答案 1

如果你只是想看到字节码:

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

但是,如果你想尝试在运行时执行此操作,那么你几乎不走运(根据设计,我们没有像表达式树这样的东西),正如另一个答案所暗示的那样。


答案 2

简单的答案是:你不能。关于这个问题,布莱恩·戈茨(Brian Goetz)有一个相关的答案。为了实现 lambda 表达式,javac 会创建一条指令,该指令将调用委托给 LambdaMetafactory 的引导方法。对于 OpenJDK,此引导方法随后使用 ASM 在运行时创建所需接口的实现。INVOKEDYNAMIC

在该方法中,回收的实例属于此 ASM 生成的类,因此您没有要读取的类文件来找出所表示的方法。在一般情况下,如何将 lambda 表达式映射到接口实现的确切决定留给运行时环境,这使您的问题更加困难。找出 lambda 表达式表示的方法的唯一方法是读取其创建的字节代码,即解释方法,如您的示例所示。testtesterPredicatemain


推荐