Lambda表达式与匿名内部类本质不同:前者编译无.class文件、用invokedynamic动态生成逻辑、变量现场读取、方法静态且无对象开销;后者生成独立.class文件、字段复制变量、走标准实例调用。

Lambda 表达式和匿名内部类在 Java 中写法相似,但字节码层面的实现完全不同:一个靠编译期“实打实建类”,一个靠运行时“按需生成逻辑”。关键不在语法,而在 JVM 怎么加载、怎么调用。
编译产物:一个有 .class 文件,一个没有
匿名内部类编译后会生成独立的 .class 文件,比如 Outer$1.class。这个文件是真实存在的,可以用 javap -c 查看完整字段、构造器和方法字节码。
Lambda 表达式编译后不生成任何额外的 .class 文件。它只在原类的字节码中留下一条 invokedynamic 指令,真实实现类由 JVM 在首次调用时动态生成并缓存。
变量捕获:一个拷贝进字段,一个按需传递
匿名内部类会把引用的局部变量(如 int x = 42)作为 final 实例字段(如 val$x)写入自己的 class,并在构造时传入赋值。字节码里能看到 putfield 和 getfield 操作。
立即学习“Java免费学习笔记(深入)”;
Lambda 不声明任何字段,也不复制变量值。它通过 invokedynamic 的引导方法,让 JVM 直接从外层方法栈帧或常量池中读取变量——值是“现场拿”,不是“提前存”。
执行路径:一个走实例方法调用,一个走静态方法+动态适配
匿名内部类对象调用方法时,走的是标准的 invokeinterface 或 invokevirtual,this 指向自身实例,字段值来自堆上保存的副本。
Lambda 对应的方法是静态方法(如 lambda$main$0),签名包含所有捕获变量作为参数;JVM 用 invokedynamic 在运行时把调用链“桥接”过去,this 指向的是外围类实例,没有额外对象分配开销。
final 限制的本质:一个防副本过期,一个防闭包混乱
两者都要求局部变量是 effectively final,但原因不同:
- 匿名内部类怕你改了原变量,而它的字段副本没同步,导致语义不一致 → 编译器检查“定义后是否被赋值”
- Lambda 不存副本,但它依赖变量在栈帧中的稳定生命周期;一旦允许修改,多线程或异步场景下容易出现可见性或时序问题 → 编译器扫描整个作用域,只要变量被写(包括
i++),就直接报错 - 成员变量不受限,因为它们属于外围实例,生命周期明确,两者都能自由读写
this.field


















