本质上来说,在Java世界里面,final、finally、finalize除了长得像一点之外,没什么共同点。但是这并不妨碍我们把它们三者放在一起进行记忆。
final联想到的发散点
- final可以实现某种程度的immutable(注意只是某种程度)
为什么说只是某种程度,假如你定义如下代码:
final
你会发现,虽然strList被final修饰,但依然可以往集合里面添加元素,这当然不是Immutable了。所以这个例子可以说明,final只能约束被它修饰的引用不会被重新赋值,但是引用指向的对象本身的行为不会被影响。如果需要控制对象本身,不允许对象本身改变,需要对象本身提供相应的方法,这里使用List.of方法(该方法Java9中才新增的)创建的list本身就是不可变的。
- Immutable
在Java中并不存在原生的不可变,如果要实现一个不可变类,我们需要做到以下几点:
a) class本身为final,别人无法扩展该类
b) 所有的成员变量定位为private和final,并且不要实现setter方法
c) 构造对象时,成员变量使用深度拷贝,避免直接赋值
d) 对于需要返回内部状态的方法,使用copy-on-write原则
(参考文章以上文章,在这里是指如果用户只读内部状态,直接获取,如果想要修改内部状态,则不能在原有数据上操作,必须copy一份到别的地方),创建私有Copy(是指copy成员变量后,返回给外部,这样就能保证成员变量不可变)。
- 在并发编程中减少额外的同步开销
因为被final修饰的变量可以在多线程的情况下共享,而不需要同步开销。关于多线程资源共享的问题后续会有专门的多线程专题。在这里只需要知道final关键字对于多线程环境下的程序运行效率有影响,合理使用有巨大的裨益。
- 省去防御性拷贝
所谓的保护性拷贝参考这篇文章:
https://blog.csdn.net/zxdfc/article/details/44064353blog.csdn.net
通过传进来的参数对象,可以对其进行一层对象拷贝,防止对对象内部的对象进行篡改。为什么说final可以省去防御性拷贝呢?这里的省去防御性拷贝是指:如果将对象内部的属性变量全部设置为final,那么在外部就不需要进行防御性拷贝,因为里面的东西都是不可变的(及时是对象,对象里面的属性也设置为final的话),这种情况下final关键字才能省去防御性拷贝。
顺带过一下浅拷贝与深拷贝的概念:
浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
- 有助于JVM进行内联
内联是指什么呢?参考这篇文章:
林林:jvm之方法内联优化zhuanlan.zhihu.com![1b009992a2a0f0421c96818330cbb59a.png](https://i-blog.csdnimg.cn/blog_migrate/e6d3778afd3f7e74063ce108236b7264.jpeg)
因为在Java程序的执行过程中,除了执行自身逻辑需要消耗资源外,还有一些额外的开销。主要是指方法栈帧的生成、参数字段的压入、栈帧的弹出、指令执行地址的跳转。
![7330b59c981f9e35502eb248272a8122.png](https://i-blog.csdnimg.cn/blog_migrate/216bd19dc0b503a8689203263fde7b38.jpeg)
具体的内联行为可能如下:
public
内联后:
public
finally联想到的发散点
- 用于关闭资源链接,推荐使用try-with-resource。值得留意的是,如果try代码中出现了System.exit(1)时,finally不会被执行。
finalize联想到的发散点
- 出现的理由
finalize方法最初之所以存在,是为了推广Java语言,兼容C++语法,做出的让步。
它本身只是一种机制,当对象不可达时,GC会判断该对象是否覆盖了finalize方法,如果没有覆盖,则将其回收。否则,如果对象没有执行过finalize方法,将其放到F-Queue队列,由低优先级线程执行该队列中对象的finalize方法。finalize方法完毕后,GC会再次判断对象是否可达,如果不可达则进行回收,否则,对象复活。可以参考:
https://www.cnblogs.com/Smina/p/7189427.htmlwww.cnblogs.com- 被抛弃的理由
Java语言规范不能保证finalize方法会被及时执行,也不能保证他们会被执行;finalize方法最多被GC调用一次;可能带来性能问题。
- 替换的机制
Java9之后,Java平台正在逐渐使用java.lang.ref.Cleaner来替换掉finalize的实现。其实我也不清楚这个cleaner为什么比finalize要好,需要进一步学习,只是知道它利用了幻象引用以及引用队列,保证在对象被彻底销毁之前做一些工作,它比finalize更加轻量、可靠。
但是即便如此,我们也不能完全依赖Cleaner来实现资源回收,因为可能会导致幻象引用堆积而出现问题。