编译后的Lambda函数中额外参数的来源探究
我正试图弄清JVM中Lambda与闭包的具体工作机制,为此编写了如下简单测试用例:
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$makeAdder$0(java.lang.Float, java.lang.Float),该方法签名显示Lambda有两个参数,而源码中仅声明了一个。
显然,额外参数用于绑定闭包中的a值,这在一定程度上解释了Java闭包如何获取绑定变量的值:将其添加到参数列表的头部。
但问题是,最终调用者如何知晓这一点?main方法的反汇编代码与源码结构一致,完全不了解闭包的实现细节,它仅向makeAdder传入一个参数,再向Lambda传入第二个参数,即仅向Lambda传一个参数。
那么第一个参数是如何传递给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
核心解答:LambdaMetafactory与闭包的参数绑定
没错,BootstrapMethods就是解决这个问题的核心。Java的Lambda并不是直接生成传统的匿名内部类,而是通过invokedynamic指令配合LambdaMetafactory动态生成实现目标接口的类实例,这个过程会自动处理闭包变量的绑定逻辑。
具体来说:
- 当
makeAdder执行到invokedynamic指令时,JVM会调用LambdaMetafactory.metafactory引导方法,它的作用是创建一个CallSite,这个CallSite会关联到实际执行Lambda逻辑的lambda$makeAdder$0方法。 - 引导方法的参数里已经明确了几个关键信息:
#63 (Ljava/lang/Object;)Ljava/lang/Object;:这是Function.apply接口方法的签名,也就是外部调用时看到的标准签名——只需要传入一个参数。#64 REF_invokeStatic Adder.lambda$makeAdder$0:(Ljava/lang/Float;Ljava/lang/Float;)Ljava/lang/Float;:这是实际执行计算逻辑的方法句柄,也就是你看到的带两个参数的静态方法。#67 (Ljava/lang/Float;)Ljava/lang/Float;:这是Lambda自身的方法类型(去掉闭包捕获变量后的签名),对应源码里b -> a + b中b的参数类型和返回值类型。
- LambdaMetafactory会动态生成一个适配器类,这个类实现了
Function接口。它的apply方法会自动把闭包捕获的变量a(也就是makeAdder调用时传入的1.23f)和外部传入的参数b合并,一起传递给lambda$makeAdder$0方法完成计算。
简单说,main方法调用f.apply(4.56f)时,实际调用的是适配器类的apply方法——这个适配器已经持有了makeAdder调用时捕获的a值,所以它会自动把a作为第一个参数,加上外部传入的b,一起传给lambda$makeAdder$0,不需要调用者关心内部的参数合并逻辑。
而编译器生成的lambda$makeAdder$0静态方法,把闭包变量和Lambda参数都作为输入,这样就不需要生成持有状态的对象(比如匿名内部类实例),既简化了实现又提升了执行效率。
内容的提问来源于stack exchange,提问作者rwallace

