@[TOC]

一、前言

作为Java后端开发者,小编在面试过程中90%都会问到的Java基础面试题: final、finally、 finalize 有什么不同?
以前都是简单说说用法和实践角度回答的.直到看了极客时间的课程在知道自己回答只是皮毛,路很长,写一篇博客出来让大家一起学习,如有侵权,联系删除哈!!!

课程地址

二、典型回答

- final
可以用来修饰类、方法、变量,分别有不同的意义,final 修饰的 class 代表不可以继承扩展,final 的变量是不可以修改的,大部分定义为常量,而 final 的方法也是不可以重写的(override)。

- finally
则是 Java 保证重点代码一定要被执行的一种机制。我们可以使用 try-finally 或者 try-catch-finally 来进行类似关闭 JDBC 连接、保证 unlock 锁、还有可以当做if else作为判断语句等动作。

- finalize
是基础类 java.lang.Object 的一个方法,它的设计目的是保证对象在被垃圾收集前完成特定资源的回收。finalize 机制现在已经不推荐使用,并且在 JDK 9 开始被标记为 deprecated。

三、深入谈final

推荐使用 final 关键字来明确表示我们代码的语义、逻辑意图,这已经被证明在很多场景下是非常好的实践,比如:

  • 我们可以将方法或者类声明为 final,这样就可以明确告知别人,这些行为是不许修改的。

如果你关注过 Java 核心类库的定义或源码, 有没有发现 java.lang 包下面的很多类,相当一部分都被声明成为 final class?在第三方类库的一些基础类中同样如此,这可以有效避免 API 使用者更改基础功能,某种程度上,这是保证平台安全的必要手段。

  • 使用 final 修饰参数或者变量,也可以清楚地避免意外赋值导致的编程错误,甚至,有人明确推荐将所有方法参数、本地变量、成员变量声明成 final。
  • final 变量产生了某种程度的不可变(immutable)的效果,所以,可以用于保护只读数据,尤其是在并发编程中,因为明确地不能再赋值 final 变量,有利于减少额外的同步开销,也可以省去一些防御性拷贝的必要。

final 也许会有性能的好处,很多文章或者书籍中都介绍了可在特定场景提高性能,比如,利用 final 可能有助于 JVM 将方法进行内联,可以改善编译器进行条件编译的能力等等。坦白说,很多类似的结论都是基于假设得出的,比如现代高性能 JVM(如 HotSpot)判断内联未必依赖 final 的提示,要相信 JVM 还是非常智能的。类似的,final 字段对性能的影响,大部分情况下,并没有考虑的必要。

从开发实践的角度,我不想过度强调这一点,这是和 JVM 的实现很相关的,未经验证比较难以把握。我的建议是,在日常开发中,除非有特别考虑,不然最好不要指望这种小技巧带来的所谓性能好处,程序最好是体现它的语义目的。

四、深入谈finally

对于 finally,明确知道怎么使用就足够了,我们在需要关闭的连接等资源,更推荐使用 Java 7 中添加的 try-with-resources 语句,简称TWR。因为通常 Java 平台能够更好地处理异常情况,编码量也要少很多,何乐而不为呢。

- try-catch-finally

try (InputStream is = new FileInputStream("test.txt")) {
	is.read();
	...
} catch(Exception e) {
	e.printStackTrace();
} finally {
	is.close();
	//无需添加关闭InputStream输入流的代码,其close()方法会自行调用
}

- TWR(推荐)

//无需添加关闭InputStream输入流的代码,其close()方法会自行调用
try (InputStream is = new FileInputStream("test.txt")) {
	is.read();
	...
} catch(Exception e) {
	e.printStackTrace();
}

- fianlly 不会被执行的情况

  1. try-cach 异常退出

    	try{
    		system.exit(1)
    	}catch(Exception e) {
    		e.printStackTrace();
    	}finally{
    		System.out.println("看得到我吗?");
    	}
    
  2. try-cach 程序正常退出

    	try{
    		system.exit(0)
    	}catch(Exception e) {
    		e.printStackTrace();
    	}finally{
    		System.out.println("看得到我吗?");
    	}
    
  3. 死循环

    	try{
    	  while(ture){
    	    System.out.println("循环死你!!");
    	  }
    	}catch(Exception e) {
    		e.printStackTrace();
    	}finally{
    		System.out.println("看得到我吗?");
    	}
    

五、深入谈finalize

finalize 的执行是和垃圾收集关联在一起的,一旦实现了非空的 finalize 方法,就会导致相应对象回收呈现数量级上的变慢,有人专门做过 benchmark,大概是 40~50 倍的下降。

因为,finalize 被设计成在对象被垃圾收集前调用,这就意味着实现了 finalize 方法的对象是个“特殊公民”,JVM 要对它进行额外处理。finalize 本质上成为了快速回收的阻碍者,可能导致你的对象经过多个垃圾收集周期才能被回收。

有人也许会问,我用 System.runFinalization​() 告诉 JVM 积极一点,是不是就可以了?也许有点用,但是问题在于,这还是不可预测、不能保证的,所以本质上还是不能指望。实践中,因为 finalize 拖慢垃圾收集,导致大量对象堆积,也是一种典型的导致 OOM 的原因。

从另一个角度,我们要确保回收资源就是因为资源都是有限的,垃圾收集时间的不可预测,可能会极大加剧资源占用。这意味着对于消耗非常高频的资源,千万不要指望 finalize 去承担资源释放的主要职责,最多让 finalize 作为最后的“守门员”,况且它已经暴露了如此多的问题。这也是为什么我推荐,资源用完即显式释放,或者利用资源池来尽量重用。

Java 平台目前在逐步使用 java.lang.ref.Cleaner 来替换掉原有的 finalize 实现。Cleaner 的实现利用了幻象引用(PhantomReference),这是一种常见的所谓 post-mortem 清理机制,它比 finalize 更加轻量、更加可靠。

六、总结

这是大部分都来自极客时间杨老师的讲解,自己整理一下自己可以看懂的部分,加上了一些例子为了后面学习和复习使用,如果对你有用点个赞呗!!!

Q.E.D.