关于Java闭包为什么规定局部变量是final

A closure is a callable object that retains information from the scope where it was created.
闭包是一个可调用对象,它保留了来自于创建该对象的作用域的信息。


  • 本文仅以一种闭包形式为例。交流探讨,如有误请批评指正。

Java规定:闭包函数使用的局部变量必须是final或者effectively final ( 等效 final ) 的。但是,从直观上看,即使在方法体内改了局部变量,也不像能导致什么谬误的样子。所以,这个final的规矩让人心生疑惑。

  • 先po代码(来自《On Java 8》):
// lambda使用的局部变量必须是final或等效final...

// 基本类型
class Closure6 {
    IntSupplier makeFun(int x) {  // IntSupplier接口中只有一个方法getAsInt(),无参,返回值类型int.
        int i = 0;
        i++;
        x++;
        // return () -> x + i;  // 编译器报错: Variables in lambda expressions must be final or effectively final.
        						// 即:lambda表达式中的变量必须是final 或者 effectively final.
        // 不报错的做法:
        final int iFinal = i;  // final关键字在这里很多余.
        final int xFinal = x;  // 因为这两个变量赋值后没有做任何更改,是等效final的.
        return () -> xFinal + iFinal;
    }
}

// 对象引用
class Closure9 {
    Supplier<List<Integer>> makeFun() {  // Supplier接口中只有一个方法get(),无参,返回<>中类型,此处即 List<Integer>.
        List<Integer> ai = new ArrayList<>();
//        ai = new ArrayList<>(); // Reassignment
        return () -> ai;  // 若前一行不注释, 则这里报错.
    }
}

class Closure1 {
    int i;
    IntSupplier makeFun(int x) {
        return () -> x + i++;  // 使用类成员变量时,可以更改而不报错。
    }
}

这两个报错展示了文章开头的规则。那么这是为啥嘞?

  • 我们已经知道,一个局部变量在它的作用域之外是不存在的,那么把它放在lambda表达式里并返回至作用域外,看起来好像需要java的“特许”。

  • 根据这个猜想,我原以为:java出于某种善意避免程序员犯错,所以立下了规矩,即:被lambda“捕捉”的局部变量必须是final或者"等效final"的,来避免变量被重复访问修改,从而导致confusion(混淆)。

  • 但遗憾的是,我高估了java的“善意”了,通过RednaxelaFX大佬的解析习得:实际上java只是copy了一份value到表达式中(capture-by-value),而不是capture-by-reference(比如C#把被捕获的局部变量“提升”(hoist)到对象里,使变量依托于对象存在),所以lambda使用的变量跟最初定义的局部变量(包括基本类型标识符和对象引用)彻底脱钩了。lambda表达式访问的只是一个副本。

  • 而为了掩饰这种“简单粗暴”导致的“变量不能再次访问”,java干脆告诉你:“变量定义之后就别改了哈”,好像不能再次访问的原因仅仅是这个规定而已。但实际上由于java并没有实现capture-by-reference,因此对于一个离开了作用域就不复存在的局部变量,你即便想改,也改不了。所以,这个规定好像脱裤子放屁,掩耳盗铃了。

  • 被lambda使用的类成员变量则没有这样的束缚,这是因为,(非static)成员变量只依赖于对象存在。而这个对象以一种看不见的“this”方式存在于lambda表达式的参数列表中,所以垃圾回收器不会去伤害这个对象(这个原则对普通函数同样适用)。故,类成员变量不需要被主动capture(捕获),这也印证了代码在Closure1处为何不报错,因为变量 i 始终有参数中隐含的this对象可以依赖。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值