如果您有像
这样的匿名类Predicate<String> isEmpty = new Predicate<String>() {
public boolean test(String t) {
return t.isEmpty();
}
};
传递给isEmpty
的引用的库可以检查字节代码以查看它的作用并可能对其进行操作。有没有办法可以为lambdas做这个?
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
}
在tester
中引用test
如何找到调用哪种方法?
答案 0 :(得分:11)
如果您只想查看字节码:
javap -c -p -v classfile
^disassemble
^private methods
^verbose, including constant pool and bootstrap methods attribute
但是,如果你想在运行时尝试这样做,那么你很幸运(设计上,我们没有像Expression Trees这样的东西),正如另一个答案所暗示的那样。
答案 1 :(得分:7)
简单的答案是:你做不到。这个问题有一个related answer of Brian Goetz。为了实现lambda表达式, javac 创建了一个INVOKEDYNAMIC
指令,该指令将调用委托给LambdaMetafactory
的bootstrap方法。对于OpenJDK,此引导方法然后使用ASM在运行时创建所需接口的实现。
在test
方法中,检索到的实例tester
属于此ASM生成的类,因此您无法读取类文件以查找Predicate
表示的方法。在一般情况下,如何将lambda表达式映射到接口实现的确切决定留给了运行时环境,这使得您的问题变得更加困难。找出lambda表达式所代表的方法的唯一方法是读取其创建的字节代码,即解释main
方法,为您的示例。
答案 2 :(得分:3)
毕竟,我找到了一种方法来获取一个相当可靠的lambda表达式的类文件(但当然仍然依赖于实现细节。)对于实验性实现,我现在使用的是Java agent允许类的重新转换,但它本身实现为无操作,我只对作为参数传递的二进制数组感兴趣。
通过已注册的Java代理获取Instrumentation
实例后,会注册一个ClassFileTransformer
,然后一旦重新传输 {{1},就会通过二进制表示通知}。这当然是一个相当hacky的解决方案,并且一旦lambdas的内部实现在未来发生变化,可能会破坏。
不幸的是,我没有找到任何关于代理如何在兼容标准的JVM上使用lambda表达式的合成类进行类重新转换的文档。
答案 3 :(得分:0)
我找到了另一种方法:通过修补InnerClassLambdaMetafactory
,可以在lambda类中添加注释,指向实现方法。这可以在运行时通过加载代理并重新转换元数据来实现。有关详细信息,请参阅my Blog。