Java编译器通过“值拷贝+字段注入”处理局部内部类访问外部局部变量:生成private final字段(如val$count)、修改构造方法传参并赋值;变量须effectively final,确保拷贝值唯一确定;基本类型拷贝值,引用类型拷贝地址;反编译可见val$字段及getfield访问。

Java 编译器在处理局部内部类时,并不是让内部类直接访问外部方法的栈上变量,而是通过“值拷贝 + 字段注入”的方式,在编译期就把局部变量固化为内部类的私有成员字段。
编译期生成隐式字段
当局部内部类引用了外部方法的局部变量(比如 int count = 5;),javac 会做两件事:
- 在内部类的字段表中自动添加一个私有 final 字段,例如 private final int val$count;
- 修改内部类的构造方法签名,使其接收该变量的值作为参数,例如 InnerClass(OuterClass this$0, int count)
- 在构造方法体中,把传入的 count 赋值给 val$count
变量必须是事实上 final
编译器会在语法分析阶段静态检查:只要某个局部变量在声明后未被重新赋值,就认定它是“事实上 final”。一旦出现第二次赋值(如 count = 10;),编译直接失败,报错 “local variables referenced from an inner class must be final or effectively final”。
这个限制不是为了语法美观,而是确保拷贝那一刻的值是唯一且确定的——因为后续所有对 val$count 的读取,都只依赖这个初始化值,和原始栈变量再无关联。
立即学习“Java免费学习笔记(深入)”;
基本类型与引用类型的处理一致
无论是 int 还是 List<String>,编译器都执行值拷贝:
- 基本类型:拷贝数值本身(如 42 → val$count = 42)
- 引用类型:拷贝的是对象引用地址(如指向堆中某个 ArrayList 实例),不是对象副本;因此内部类仍可调用 list.add(),但不能让变量指向新对象(list = new ArrayList<>(); 会破坏 effectively final)
反编译可验证这一机制
用 javap -c 查看编译后的内部类字节码,你会看到:
- 字段区明确列出 val$count 等带 val$ 前缀的私有 final 字段
- 构造方法指令中包含 iload_1(加载参数)→ putfield(写入字段)
- 内部类方法体中访问该变量时,实际执行的是 getfield 指令,读取的是自身字段,而非外部栈帧


















