提问人:rwallace 提问时间:1/23/2023 更新时间:1/23/2023 访问量:85
编译的 lambda 函数中的额外参数从何而来?
Where does the extra parameter in a compiled lambda function come from?
问:
我试图弄清楚 lambda 和闭包在 JVM 中是如何工作的。为此,我尝试编译了这个简单的测试用例:
import java.util.function.*;
class Adder {
static Function<Float, Float> makeAdder(Float a) {
return b -> a + b;
}
public static void main(String[] args) {
Function<Float, Float> f = makeAdder(1.23f);
System.out.println(f.apply(4.56f));
}
}
反汇编生成的字节码很有趣:
static java.util.function.Function<java.lang.Float, java.lang.Float> makeAdder(java.lang.Float);
descriptor: (Ljava/lang/Float;)Ljava/util/function/Function;
flags: (0x0008) ACC_STATIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokedynamic #7, 0 // InvokeDynamic #0:apply:(Ljava/lang/Float;)Ljava/util/function/Function;
6: areturn
LineNumberTable:
line 4: 0
Signature: #48 // (Ljava/lang/Float;)Ljava/util/function/Function<Ljava/lang/Float;Ljava/lang/Float;>;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: ldc #11 // float 1.23f
2: invokestatic #12 // Method java/lang/Float.valueOf:(F)Ljava/lang/Float;
5: invokestatic #18 // Method makeAdder:(Ljava/lang/Float;)Ljava/util/function/Function;
8: astore_1
9: getstatic #23 // Field java/lang/System.out:Ljava/io/PrintStream;
12: aload_1
13: ldc #29 // float 4.56f
15: invokestatic #12 // Method java/lang/Float.valueOf:(F)Ljava/lang/Float;
18: invokeinterface #30, 2 // InterfaceMethod java/util/function/Function.apply:(Ljava/lang/Object;)Ljava/lang/Object;
23: invokevirtual #35 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
26: return
LineNumberTable:
line 9: 0
line 10: 9
line 11: 26
private static java.lang.Float lambda$makeAdder$0(java.lang.Float, java.lang.Float);
descriptor: (Ljava/lang/Float;Ljava/lang/Float;)Ljava/lang/Float;
flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: invokevirtual #41 // Method java/lang/Float.floatValue:()F
4: aload_1
5: invokevirtual #41 // Method java/lang/Float.floatValue:()F
8: fadd
9: invokestatic #12 // Method java/lang/Float.valueOf:(F)Ljava/lang/Float;
12: areturn
LineNumberTable:
line 4: 0
以上有些是清楚的,有些则不那么清楚。我现在最困惑的部分是 lambda 函数的实现。该签名表明 lambda 有两个参数,即使它只用一个参数声明。lambda$makeAdder$0(java.lang.Float, java.lang.Float)
嗯,很明显,额外的一个是做什么用的;这是为了绑定到闭包中的价值。因此,在某种程度上,这回答了如何为 Java 闭包提供绑定变量值的问题:它们被附加到参数列表之前。a
但是,最终的来电者是如何知道这一点的呢?反汇编的代码看起来与源代码同构,即完全不知道闭包是如何实现的。它似乎向 提供了一个参数,然后向 lambda 提供了第二个参数。换句话说,只向 lambda 提供一个参数。main
makeAdder
第一个参数是如何提供给 lambda 的?
它与反汇编代码的最后一部分有什么关系吗?BootstrapMethods
BootstrapMethods:
0: #56 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#63 (Ljava/lang/Object;)Ljava/lang/Object;
#64 REF_invokeStatic Adder.lambda$makeAdder$0:(Ljava/lang/Float;Ljava/lang/Float;)Ljava/lang/Float;
#67 (Ljava/lang/Float;)Ljava/lang/Float;
InnerClasses:
public static final #74= #70 of #72; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
答:
相关方面在输出中,但您没有将其包含在您的问题中。在 javap 输出的最后:(确保使用 !javap
javap -c -v
SourceFile: "Test.java"
BootstrapMethods:
0: #56 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#63 (Ljava/lang/Object;)Ljava/lang/Object;
#64 REF_invokeStatic Adder.lambda$makeAdder$0:(Ljava/lang/Float;Ljava/lang/Float;)Ljava/lang/Float;
#67 (Ljava/lang/Float;)Ljava/lang/Float;
InnerClasses:
public static final #74= #70 of #72; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
这。。。并没有真正有用,但它是使所有这些工作的基础机制。
相关的调用是字节码指令。 是动态的:第一次遇到JVM会走一条慢路,并执行一些代码,这些代码可以任意决定这个调用的实际结果。同一调用的任何进一步执行都不再这样做 - 它们会“记住”。因此,调用可以导致您想要的任何类型的实际方法调用(当然,它最终会成为该方法的部分应用),并且速度很快。invokedynamic
invokedynamic
invokedynamic
invokedynamic
invokedynamic
lambda$makeAdder$0(float, float)
javap
的输出并不是那么有启发性。它的许多关键移动部分实际上都在类中,这是 JVM 中一个真正的类,其源代码是核心 JDK 的一部分,因此是开放的。java.lang.invoke.LambdaMetaFactory
如果你有兴趣完全理解它是如何工作的,这个关于invokedynamic
的baeldung教程可能是你想从上到下浏览的内容。
评论
1.23f
4.56f
1.23
4.56
main
makeAdder
1: invokedynamic #7, 0