Java编译器通过值复制、合成字段和构造器传参三步实现匿名内部类捕获局部变量,要求变量为final或effectively final以保证值恒定;成员变量则通过隐式this引用访问。

Java 编译器在处理匿名内部类捕获局部变量时,并不是“让内部类直接访问外部栈帧”,而是通过值复制 + 合成字段 + 构造器传参三步完成的。整个过程是编译期静态完成的,不依赖运行时机制。
变量被悄悄“复印”进内部类
当你写:
int count = 10;
new Thread(() -> System.out.println(count)).start();
编译器实际生成的等效代码类似:
立即学习“Java免费学习笔记(深入)”;
class Outer$1 implements Runnable {
final int val$count;
Outer$1(int count) { this.val$count = count; }
public void run() { System.out.println(val$count); }
}
→ count 的值(不是引用)被复制为 val$count,作为内部类的一个隐式合成字段,并在构造时传入。
final 或 effectively final 是编译器的“信任凭证”
这个复制动作之所以能安全进行,前提是编译器能静态确认该变量不会再变。否则会出现副本与原始值脱节:
- 如果允许修改 count,外部改了但 val$count 还是旧值 → 语义错乱
- 如果还要同步更新两边 → 栈变量可能已销毁,无法寻址 → 不可行
- 所以 Java 选择最简方案:禁止修改 → 用 final 或 effectively final 来保证“值恒定”
成员变量不受限,因为走的是另一条路
你写 this.name 或 flag(非局部),匿名类并不复制它,而是持有对外部实例的引用(即隐式传入 this)。这些变量本身就在堆上,生命周期由对象管理,不存在栈帧销毁问题。
Lambda 和匿名类底层一致,只是语法糖
Lambda 表达式在字节码层面也会生成类似 Outer$1 的合成类,同样有 val$xxx 字段和构造传参逻辑。Java 8 引入 effectively final 后,只是省去了显式写 final 的语法,约束强度反而更高——只要赋值两次,立刻编译失败,不给模糊空间。


















