老哥,Java 中 final 和 effectively final 到底有什么区别?

简介: 老哥,Java 中 final 和 effectively final 到底有什么区别?

之前写了一篇 Lambda 的入门文章,有小伙伴很认真地参考着学习了一下,并且在学习的过程中提出了新的问题:“老哥,当我在使用 Lambda 表达式中使用变量的时候,编译器提醒我‘Local variable limit defined in an enclosing scope must be final or effectively final’,意思就是‘从 Lambda 表达式引用的本地变量必须是最终变量或实际上的最终变量’,这其中的 final 和 effectively final 到底有什么区别呢?”


看到这个问题,我忍不住调皮了,不假思索地回答说:“当然是 effectively 啦!”


那老弟竟然没被我气晕过去,紧接着说:“老哥,我知道你幽默,你风趣,这节骨眼你能不能细致给我说说这两者之间的区别啊?在线等。”


然后我细细致致地又给他聊了半个多小时,总算是解释清楚了。临了,他还特意叮嘱我说:“老哥,别忘了写篇文章啊!让更多的小伙伴知道这个区别。”




现在的年轻人啊,真特么有心,未来是你的!我承认我是个负责任的男人,既然如此,下面我就来详细地谈一谈 final 和 effectively final 之间的区别。


先来看看官方(Java 8 的规格说明书)是怎么说的,如下:


a final variable can be defined as An entity once that cannot be changed nor derived from later and an effectively final defined as variable or parameter whose value is never changed after it is initialized is effectively final.

就像你看到的,上面这段解释并没有针对关键词“effectively”进行详细的说明——看得云里雾里的,等于没看。


来看下面这段代码:


int limit = 10;
Runnable r = () -> {
    limit = 5;
    for (int i = 0; i < limit; i++) {
        System.out.println(i);
    }
};
new Thread(r).start();


编译器会提醒我们“Lambda 表达式中的变量必须是 final 或者 effectively final”,按照编译器的提示,我们把 limit 变量修饰为 final,但这时候,编译器提示了新的错误。

aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xMTc5Mzg5LTEyOGNmMjQ0ZjNmZjE3ZjEucG5n.png



这次的错误就很明确了,final 变量是不能被重新赋值的——众所周知,这正是 final 关键字的作用——于是我们把 limit = 5 这行代码去掉。


final int limit = 10;
Runnable r = () -> {
    for (int i = 0; i < limit; i++) {
        System.out.println(i);
    }
};
new Thread(r).start();



考虑到 limit 在接下来的代码中并未被重新赋值,我们可以将 final 关键字去掉。


int limit = 10;
Runnable r = () -> {
    for (int i = 0; i < limit; i++) {
        System.out.println(i);
    }
};
new Thread(r).start();


代码仍然可以正常编译,正常运行,那么此时的 limit 变量就是“effectively final”的。由于 limit 在接下来的代码中没有被重新赋值,编译器就被欺骗了,想当然地认为 limit 就是一个 final 变量(实际上的最终变量)。


假如 limit 在声明为普通的变量(没有 final 修饰)后又被重新赋值了,那也就不可能成为“effectively final”了。


因此得出的结论是,“effectively final”是一个行为类似于“final”的变量,但没有将其声明为“final”变量,关键就在于编译器是怎么看待的。


相关文章
|
2月前
|
Java 测试技术
Java浮点类型详解:使用与区别
Java中的浮点类型主要包括float和double,它们在内存占用、精度范围和使用场景上有显著差异。float占用4字节,提供约6-7位有效数字;double占用8字节,提供约15-16位有效数字。float适合内存敏感或精度要求不高的场景,而double精度更高,是Java默认的浮点类型,推荐在大多数情况下使用。两者都存在精度限制,不能用于需要精确计算的金融领域。比较浮点数时应使用误差范围或BigDecimal类。科学计算和工程计算通常使用double,而金融计算应使用BigDecimal。
1410 102
|
3月前
|
存储 缓存 人工智能
Java int和Integer的区别
本文介绍了Java中int与Integer的区别及==与equals的比较机制。Integer是int的包装类,支持null值。使用==比较时,int直接比较数值,而Integer比较对象地址;在-128至127范围内的Integer值可缓存,超出该范围或使用new创建时则返回不同对象。equals方法则始终比较实际数值。
136 0
|
1月前
|
安全 Java API
Java SE 与 Java EE 区别解析及应用场景对比
在Java编程世界中,Java SE(Java Standard Edition)和Java EE(Java Enterprise Edition)是两个重要的平台版本,它们各自有着独特的定位和应用场景。理解它们之间的差异,对于开发者选择合适的技术栈进行项目开发至关重要。
236 1
|
4月前
|
存储 Java C语言
Java List 复制:浅拷贝与深拷贝方法及区别
我是小假 期待与你的下一次相遇 ~
454 1
|
3月前
|
安全 算法 Java
Java 中 synchronized 与 AtomicInteger 的区别
在Java多线程编程中,`synchronized`和`AtomicInteger`均用于实现线程安全,但原理与适用场景不同。`synchronized`是基于对象锁的同步机制,适用于复杂逻辑和多变量同步,如银行转账;而`AtomicInteger`采用CAS算法,适合单一变量的原子操作,例如计数器更新。二者各有优劣,应根据具体需求选择使用。
107 0
|
4月前
|
算法 Java 数据库连接
Java 与 C++ 区别深入剖析及应用实例详解
本文深入剖析了Java和C++两种编程语言的区别,从编译与执行机制、面向对象特性、数据类型与变量、内存管理、异常处理等方面进行对比,并结合游戏开发、企业级应用开发、操作系统与嵌入式开发等实际场景分析其特点。Java以跨平台性强、自动内存管理著称,适合企业级应用;C++则因高性能和对硬件的直接访问能力,在游戏引擎和嵌入式系统中占据优势。开发者可根据项目需求选择合适语言,提升开发效率与软件质量。附面试资料链接:[点此获取](https://pan.quark.cn/s/4459235fee85)。
420 0
|
5月前
|
Java
Java 中 Exception 和 Error 的区别
在 Java 中,`Exception` 和 `Error` 都是 `Throwable` 的子类,用于表示程序运行时的异常情况。`Exception` 表示可被捕获和处理的异常,分为受检异常(Checked)和非受检异常(Unchecked),通常用于程序级别的错误处理。而 `Error` 表示严重的系统级问题,如内存不足或 JVM 错误,一般不建议捕获和处理。编写程序时应重点关注 `Exception` 的处理,确保程序稳定性。
131 0
|
6月前
|
Java 编译器 程序员
java中重载和多态的区别
本文详细解析了面向对象编程中多态与重载的概念及其关系。多态是OOP的核心,分为编译时多态(静态多态)和运行时多态(动态多态)。编译时多态主要通过方法重载和运算符重载实现,如Java中的同名方法因参数不同而区分;运行时多态则依赖继承和方法重写,通过父类引用调用子类方法实现。重载是多态的一种形式,专注于方法签名的多样性,提升代码可读性。两者结合增强了程序灵活性与扩展性,帮助开发者更好地实现代码复用。
233 0
|
9月前
|
Java 程序员 调度
Java 高级面试技巧:yield() 与 sleep() 方法的使用场景和区别
本文详细解析了 Java 中 `Thread` 类的 `yield()` 和 `sleep()` 方法,解释了它们的作用、区别及为什么是静态方法。`yield()` 让当前线程释放 CPU 时间片,给其他同等优先级线程运行机会,但不保证暂停;`sleep()` 则让线程进入休眠状态,指定时间后继续执行。两者都是静态方法,因为它们影响线程调度机制而非单一线程行为。这些知识点在面试中常被提及,掌握它们有助于更好地应对多线程编程问题。
388 9
|
9月前
|
安全 Java 程序员
Java面试必问!run() 和 start() 方法到底有啥区别?
在多线程编程中,run和 start方法常常让开发者感到困惑。为什么调用 start 才能启动线程,而直接调用 run只是普通方法调用?这篇文章将通过一个简单的例子,详细解析这两者的区别,帮助你在面试中脱颖而出,理解多线程背后的机制和原理。
440 12
下一篇
开通oss服务