你能在运行时检查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类添加一个指向实现方法的注释。 这可以在运行时通过加载代理并重新转换元数据来实现。 有关详情,请参阅我的博客