Java 的 lambda 捕获局部变量用的是值拷贝:创建 lambda 时把变量当前值复制进生成的实例字段。如果允许事后修改原变量,lambda 里看到的还是旧拷贝,内外两处状态悄悄分叉,极易产生隐蔽 bug。与其允许一个语义陷阱,语言直接禁止:捕获的局部变量必须 final 或 effectively final(赋值后不再修改),保证拷贝值与变量永远一致,消除了「读到的是副本还是本尊」的二义性。
对比可以帮助理解:JavaScript 闭包捕获的是变量本身(绑定),内外共享可变状态,循环变量闭包陷阱是其经典坑;Java 选择了更保守的拷贝语义。实例字段/静态变量不受此限制,因为它们在堆上,lambda 持有的是对象引用,天然共享。常见的「绕过」手段:用单元素数组、AtomicInteger、AtomicReference 包一层——本质是把可变性移到堆对象里,lambda 捕获的引用本身不变。追问方向:lambda 与匿名内部类在 this 语义上的差异(lambda 的 this 是外围类实例,匿名类是自身)、捕获变量的 lambda 每次创建都是新对象(invokedynamic 只在无捕获时缓存单例)。
(约 400 字)