扩展 - final、finally、finalize
# final、finally、finalize
# final
final 可以用来修饰 Java 的类、方法、变量,final 修饰的类不能被继承扩展;final 修饰方法不能被重写;final 修饰的变量是不可修改的。
推荐使用 final 关键字用来表明代码语义和逻辑意图。
使用 final 修饰的类和方法可以明确得表达当前的类和方法是不能被修改的,java.lang 包中的很多类都被声明为 final 如 String 类,防止 API 使用者更改基础功能;使用 final 修饰的变量或参数,可以有效避免意外赋值导致的程序错误。
final 可能对性能有好处,但在大部分场景下没有考虑的必要。
final 在某种程度上产生了不可变(immutable)效果,可以用于保护只读数据。特别在并发编程中,可以减少额外的同步开销,也可以省去一些防御性拷贝的必要。
final 并不等同于 immutable,比如一个集合对象使用 final 修饰,只是表示该对象引用不可修改,但还可以进行 add 等操作。可以使用 List.of() 来创建不可变的集合(因为该 List 是不可变的)
final List<String> list = new ArrayList();
list.add("test"); // 不会报错,list 可以执行添加操作
List<String> unmodifiableList = List.of("test");
unmodifiableList.add("etst"); // 会报错,因 List.of 创建的是不可变的集合
2
3
4
immutable 在某些场景是很好的实践,Java 类若要实现 immutable 则需要满足如下要求:
- 使用 final 修饰类,防止被扩展而绕过限制
- 类成员变量都声明为 private 和 final,并且不实现 setter 方法
- 通常构造对象时,成员变量使用深度拷贝来初始化,而不是直接赋值,这是一种防御性措施,因为无法保证输入参数不被修改
- 如果确定需要 getter 方法,或者需要返回内部状态的方法,则遵守 Copy-On-Write 原则,创建私有的 copy
延伸扩展:Java 中 Copy-On-Write 原则通常用于解决读多写少是的并发问题。比如 ArrayList 是不保证线程安全的,在并发场景下如果要保证线程安全,则需要给 ArrayList 添加读写所,读要读锁,写要写锁,读与读不互斥,读与写是互斥的,写与写也是互斥的。而在读多写少的场景,频繁的加锁有点浪费资源。固有了 CopyOnWriteArrayList ,其设计思想:
- 底层的数组 array 变量使用 volatile 修饰,volatile 修饰的变量是内存可见的,可以保证任意线程在任意时间读取到最新变量值
- 读操作不加锁
- 写操作添加互斥锁,并先拷贝数组 array 的副本,在对副本进行写操作,最后将副本对象的引用赋值给数组 array,由于添加了互斥锁,固该过程是原子性的。
# finally
finally 是 Java 为保证某段代码一定会被执行的机制。使用 try-finally、try-catch-finally 进行关闭 JDBC 连接、关闭文件流、锁的 unlock 等。日常中更加推荐 Java 7 引入的 try-with-resources,让 Java 平台处理资源的回收。
try {
System.exit(); // 程序中断
} catch () {
System.out.println("hello"); // 这里不会被执行
}
2
3
4
5
finally 已被证实是不好的实践。因为 finaly 被设计成在对象被垃圾回收前执行,而 JVM 需额外进行处理,导致对象可能经过几个垃圾收集周期才被回收,可能造成大量对象堆积,引发 OOM 问题。
# finalize
finalize 是 Java 基础类 java.lang.Object 的一个方法,用来保证对象在被垃圾回收前完成特定资源的回收。finalize 机制目前已经不被推荐使用,在 JDK 9中被标记为 deprecated,因为无法保证 finalize 什么时候被执行以及执行得是否符合预期,使用不当可能影响性能,甚至导致程序死锁、挂起等问题。