查看原文
其他

Java Final与Effectively Final

ImportNew ImportNew 2021-03-11

(给ImportNew加星标,提高Java技能)

编译:ImportNew/覃佑桦

baeldung.com/java-effectively-final


1. 介绍


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


本文将介绍该功能的起源以及编译器处理 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 关键字看能否编译并运行:


@FunctionalInterface
public interface FunctionalInterface {
void testEffectivelyFinal();
default void test() {
int effectivelyFinalInt = 10;
FunctionalInterface functionalInterface
= () -> System.out.println("Value of effectively variable is : " + effectivelyFinalInt);
}
}


重新赋值或者改变 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 变量,仅用作字符串连接:


public static void main(String[] args) {
final String hello = "hello";
final String world = "world";
String test = hello + " " + world;
System.out.println(test);
}

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


public static void main(String[] var0) {
String var1 = "hello world";
System.out.println(var1);
}


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


4. 原子操作


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


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


最佳实践: baeldung.com/java-8-lambda-expressions-tips

反模式教程: baeldung.com/java-lambda-effectively-final-local-variables


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


public static void main(String[] args) {
AtomicInteger effectivelyFinalInt = new AtomicInteger(10);
FunctionalInterface functionalInterface = effectivelyFinalInt::incrementAndGet;
}


5. 总结


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


推荐阅读  点击标题可跳转

终于明白为什么要加 final 关键字了!

深入理解 Java final 变量的内存模型

JVM 解剖公园(17): 信任非 static final 字段


看完本文有收获?请转发分享给更多人

关注「ImportNew」,提升Java技能

好文章,我在看❤️


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存