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

如果你有像这样的匿名类

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

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

 Predicate isEmpty = String::isEmpty; 

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

 public class Main { public static void test(Predicate tester) { System.out.println("tester.getClass()= " + tester.getClass()); System.out.println("tester.getClass().getClassLoader()="+ tester.getClass().getClassLoader()); } public static void main(String... args) { Predicate 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."":()V 4: return public static void test(java.util.function.Predicate); 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."":()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."":()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 } 

tester中引用tester如何找到调用哪个方法?

如果您只想查看字节码:

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

但是,如果你想在运行时尝试这样做,那么你很幸运(根据设计,我们没有像Expression Trees这样的东西),正如另一个答案所暗示的那样。

简单的答案是:你做不到。 关于Brian Goetz这个问题有一个相关的答案 。 为了实现lambda表达式, javac创建了一个INVOKEDYNAMIC指令,该指令将调用委托给LambdaMetafactory的bootstrap方法。 对于OpenJDK,此引导方法然后使用ASM在运行时创建所需接口的实现。

test方法中,retreived实例tester程序属于此ASM生成的类,因此您无需读取类文件以查找Predicate代表的方法。 在一般情况下,如何将lambda表达式映射到接口实现的确切决定留给运行时环境,这使得您的问题变得更加困难。 找出lambda表达式所代表的方法的唯一方法是读取其创建的字节代码,即解释main方法,例如。

毕竟,我找到了一种方法来获取一个相当可靠的lambda表达式的类文件(但当然仍然依赖于实现细节。)对于实验性实现,我现在使用的Java代理允许重新转换类但它本身实现为无操作,我只对作为参数提交的二进制数组感兴趣。

通过已注册的Java代理获取Instrumentation实例后,可以注册一个ClassFileTransformer ,然后在重新传输 lambdaInstance.getClass()通过二进制表示通知。 这当然是一个相当hacky的解决方案,并且一旦lambdas的内部实现在未来发生变化,可能会破坏。

不幸的是,我没有找到任何关于代理如何在标准兼容的JVM上使用lambda表达式的合成类进行类重新转换的文档。

我找到了另一种方法:通过修补InnerClassLambdaMetafactory ,可以向lambda类添加一个指向实现方法的注释。 这可以在运行时通过加载代理并重新转换元数据来实现。 有关详情,请参阅我的博客