JavaFinal与EffectivelyFinal

1. 介绍

Java 8 引入的众多功能中,其中一个最有趣的功能是 effectively final。即不用 final 修饰符也能达到同样的效果。

同德网站建设公司创新互联公司,同德网站设计制作,有大型网站制作公司丰富经验。已为同德上1000+提供企业网站建设服务。企业网站搭建\外贸营销网站建设要多少钱,请找那个售后服务好的同德做网站的公司定做!

本文将介绍该功能的起源以及编译器处理 effectively final 与 final 关键字的不同之处。此外,还会通过一个 effectively final 变量的问题案例给出解决方案。

2. Effectively Final 的起源

简而言之,如果对象或基础类型的变量在初始化后值不发生改变,则可以把它们看做 effectively final。只要不改变对象引用,即使引用的对象发生状态改变,该对象也是 effectively final。

在 Java 引入该功能之前,不能在匿名类中使用非 final 局部变量。此外,也不能在匿名类、内部类和 lambda 表达式中多次赋值。新功能的加入节省了为 effectively final 变量输入 final 关键字的工作。

匿名类是一种内部类,不能访问非 final 变量或 effectively final 变量,也无法按照 JLS 8.1.3 的规定在其封闭作用域内的变量进行修改。lambda 表达式也有类似的限制,修改变量可能会带来并发问题。

docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.1.3

3. Final vs Effectively Final

要确认一个 final 变量是不是 effectively final,最简单的办法就是删除 final 关键字看能否编译并运行:

Java Final与Effectively Final

重新赋值或者改变 effectively final 都会报告无效代码。

3.1 编译器处理

JLS 4.12.4 指出,从方法参数或局部变量中删除 final 修饰符且不产生编译错误,则该变量为 effectively final。在程序中为变量声明加上 final 关键字,那么该变量也会变成 effectively final。

docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.12

与 final 变量不同,Java 编译器不会对 effectively final 变量进行额外优化。

下面这个简单的示例中声明两个 final String 变量,仅用作字符串连接:

Java Final与Effectively Final

编译器会将上面 main 方法实际执行的代码变成下面这样:

Java Final与Effectively Final

去掉 final 关键字,这些变量将被视为 effectively final。但编译器不会因为它们仅用作字符串连接而对它们优化。

4. 原子操作

在 lambda 表达式和匿名类中修改变量不是一个好习惯。因为我们不知道这些变量在方法块中会如何使用,在多线程环境中修改也可能会得到意外的结果。

关于使用 lambda 表达式的最佳实践已经有了一个教程,另外还有一个教程关于修改lambda 表达式中常见的反模式。有一种替代方案可以在这种场景中修改变量,通过原子性实现线程安全。

java.util.concurrent.atomic 提供了像 AtomicReference 和 AtomicInteger 这样的类。可以使用原子操作修改 lambda 表达式中的变量:

Java Final与Effectively Final

5. 总结

本文介绍了 final 变量和 effectively final 变量的区别,并且介绍了一种安全修改 lambda 函数变量的方案。


分享题目:JavaFinal与EffectivelyFinal
本文网址:http://ybzwz.com/article/gphcds.html