Lambda表达式通过invokedynamic指令实现按需生成,编译时不产生.class文件,运行时由LambdaMetafactory.metafactory引导动态生成实现类并绑定MethodHandle。

直接看字节码里的 invokedynamic 指令,就能确认 Lambda 是不是真“按需生成”——它不依赖预编译的 .class 文件,而是靠运行时引导方法动态组装实例。
怎么用 javap 找到 invokedynamic 指令和对应引导方法
编译完含 Lambda 的类后,执行 javap -v MyClass.class,重点盯两处:
-
Code:段里找invokedynamic行,例如:0: invokedynamic #2, 0 // InvokeDynamic #0:apply:()Ljava/util/function/Function; - 往下翻到
BootstrapMethods:段,找到编号匹配的项(比如 #2),它会明确写:REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory
这两处对上,就说明 JVM 确实是走 LambdaMetafactory.metafactory 这条路径启动的,不是退化成匿名内部类。如果看到 BootstrapMethods 缺失或指向 InnerClass 相关内容,那大概率是用了老 JVM 或开启了某些调试兼容选项。
invokedynamic 后面的参数到底传了什么
invokedynamic 指令本身不带目标方法,只带一个常量池索引;真正干活的是引导方法接收的三个固定参数:
-
MethodHandles.Lookup:JVM 自动注入,有权限访问私有静态方法(比如lambda$main$0) -
String:函数式接口抽象方法名,如"apply"或"run" -
MethodType:描述签名,如(Ljava/lang/String;)Ljava/lang/String;,它定义了“用户看到的接口行为”,和实际生成的方法可能不一致
注意:这个 MethodType 不是你写的 Lambda 形参列表,而是编译器根据函数式接口推导出的、对外暴露的调用契约。它和后面实际逻辑方法(如 lambda$makeAdder$0)的签名可以不同——捕获变量就是靠这种差异塞进去的。
为什么反编译看不到 Lambda 实际类,但运行时能 new 出来
因为 Lambda 对应的实现类根本不存在于磁盘,也不进 ClassLoader.defineClass 的常规流程。它由 LambdaMetafactory.metafactory 内部用 ASM 动态生成字节码,再通过 Unsafe.defineAnonymousClass 注入 JVM 方法区。所以:
-
javap看不到这个类文件(ls *.class也找不到MyClass$1.class) - 但运行时
lambda.getClass().getName()会返回类似MyClass$$Lambda$1/0x000000080006a040的名字,这是 JVM 运行期生成的临时类名 - 这个类不可序列化(除非显式实现
Serializable并满足约束),因为没源码、没稳定类结构
容易被忽略的关键点
第一次触发 invokedynamic 时才生成类和实例,后续调用复用同一个 CallSite;但如果你在 Lambda 里捕获了局部变量,那个变量值是在 metafactory 返回前就被固化进新类的字段里的——不是每次调用都重新读原变量。这也是为什么 Java 要求捕获变量必须是 effectively final:不是 JVM 不能改,而是改了会导致闭包看到的值和原始作用域不一致。











