学习目标:
- final、finally、finalize
- immutable
学习结论:
## final、finally、finalizefinal(最终的) 可以用来修饰类,方法,变量,有不同的意义
- 修饰类:类不能被继承。
- 修饰方法:方法不能被重写(override)。
- 修饰变量:变量不能被修改。
思考题:final 修饰变量时,真的无法修改变量的值吗?为什么?final 并不等同于 immutable(不可变得)
java.lang包下面很多类,都被声明为final class;平时使用的一些第三方库也会使用这种方法来确保一定的安全性。
final有性能优势,有助于JVM将方法内联。这一点有很多贴子和书籍上都有说。随着JVM的性能提升,判断内联未必依赖 final 的提示,要相信 JVM 还是非常智能的。如果你试图依靠这种小技巧来提升性能,需要考虑一下成本效益。
finally
finally 则是 Java 保证重点代码一定要被执行的一种机制。
finally作为异常处理的一部分,语句块中代码一定会被执行,经常被用来释放资源。可以使用 try-finally 或者 try-catch-finally 来进行类似关闭 JDBC 连接、保证 unlock 锁等动作。
锁是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问。一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。不过,某些锁可能允许对共享资源并发访问,如 ReadWriteLock 的读取锁。
有没有什么情况是不会执行finally代码块的代码呢? 可以尝试执行一下如下代码:(这是个特例,不必纠结)
//System.out.println("Print from finally"); 不会被执行。
try {
// do something
System.exit(1);
} finally{
System.out.println("Print from finally");
}
finalize
finalize 是基础类 java.lang.Object 的一个方法,它的设计目的是保证对象在被垃圾收集前完成特定资源的回收。
在Java中,由于GC的自动回收机制,理论上在对象不再被引用时,GC就会收回其内存。实际上GC不能准时的回收。因而并不能保证finalize方法会被及时地执行,垃圾对象的回收时机具有不确定性(例如:GC负责打扫你家别墅中的垃圾,它需要一个房间一个房间巡逻,你在A房间让的垃圾,它可能要很久才能来打扫A房间),也不能保证它们会被执行(程序由始至终都未触发垃圾回收)。
public class Test {
@Override
protected void finalize() throws Throwable {
System.out.println("GC,请收了我吧");
}
public static void main(String[] args) {
Test test = new Test();
test = null;//对象不再被引用
//如果不手动执行GC,可能finalize永远不会执行
System.gc();
}
}
finalize 机制现在已经不推荐使用,并且在 JDK 9 开始被标记为 deprecated。
利用上面的提到的 try-with-resources 或者 try-finally 机制,是非常好的回收资源的办法。
JDK中的又引入了Cleaner机制
- 首先它的使用方法有点繁琐,和try-with-resource机制一样,需要实现AutoCloseable接口,这样在重写的close()方法中释放资源会被自动调用回收。
- cleaner机制需要创建单独的线程去执行逻辑,这与finalize机制不同。
- 执行finalize机制的线程不可控所以cleaner机制不存在类似于先执行finalize逻辑在回收对象的问题,即只要执行了cleaner机制就不会降低垃圾回收效率。
- 但是前提是执行了cleaner机制,因为它的clean()方法还是写在重写的close()方法中等待被自动调用,所以无法保证保证被及时执行。
知识点拓展:
思考题:final 修饰变量时,真的无法修改变量的“值”吗?为什么? final 并不等同于 immutable(不可变得)
到底能不能修改呢!翠花:上酸菜。看示例:
public class Test {
public static void main(String[] args) {
final StringBuffer strBuf =new StringBuffer("0");
System.out.println(strBuf.toString());
for (int i = 1; i <= 5; i++) {
strBuf.append(i);
}
System.out.println(strBuf.toString());
}
}
执行结果:
哇!修改了。为什么呢?在Java中:
- final修饰基本类型变量时,变量变为常量,只能赋值一次。可以尝试修改试试,编译报错。
- final修改引用类型变量时,变量的引用不能修改,但是值是可以修改。上面例子已经充分说明一切了。你可以试试修改Map等其他类型。
public static void main(String[] args) {
final List strList = new ArrayList<>();
strList.add("Hello");
strList.add("world");
List unmodifiableStrList;
unmodifiableStrList = List.of("hello", "world");
unmodifiableStrList.add("again");
}
final 只能约束 strList 这个引用不可以被赋值,但是 strList 对象行为不被 final 影响,添加元素等操作是完全正常的。
如果希望对象本身是不可变的,那么需要相应的类支持不可变的行为。在上面这个例子中,List.of 方法创建的本身就是不可变 List,最后那句 add 是会在运行时抛出异常的。
Immutable不可变的
Java 语言目前并没有原生的不可变支持.实现 immutable 的类.
- 将 class 自身声明为 final,这样别人就不能扩展来绕过限制了。
- 将所有成员变量定义为 private 和 final,并且不要实现 setter 方法。
- 通常构造对象时,成员变量使用深度拷贝来初始化,而不是直接赋值,这是一种防御措施,因为你无法确定输入对象不被其他人修改。(浅拷贝:引用类型copy的引用;深拷贝:引用类型也copy值)
- 如果确实需要实现 getter 方法,或者其他可能会返回内部状态的方法,使用 copy-on-write 原则,创建私有的 copy。