Java 中 final、finally、finalize 有什么不一样?这是在 Java 面试中常常问到的问题,他们究竟有什么不一样呢?html
这三个看起来很类似,其实他们的关系就像卡巴斯基和巴基斯坦同样有基巴关系。java
那么若是被问到这个问题该怎么回答呢?首先能够从语法和使用角度出发简单介绍三者的不一样:面试
若是只回答到这里,就会没有亮点,咱们能够再深刻地去介绍三者的不一样,好比从性能、并发、对象生命周期或垃圾收集基本过程等方面去谈谈本身的理解。编程
使用 final 关键字能够明确表示代码的语义、逻辑意图,好比:安全
能够将方法或者类声明为 final,这样就能够明确告知别人,这些行为是不准修改的。 Java 核心类库的定义或源码,好比 java.lang 包下面的不少类,至关一部分都被声明成为 final class,好比咱们常见的 String 类,在第三方类库的一些基础类中一样如此,这能够有效避免 API 使用者更改基础功能,某种程度上,这是保证平台安全的必要手段。并发
使用 final 修饰参数或者变量,也能够清楚地避免意外赋值致使的编程错误,甚至,有人明确推荐将全部方法参数、本地变量、成员变量声明成 final。ide
final 变量产生了某种程度的不可变(immutable)的效果,因此,能够用于保护只读数据,尤为是在并发编程中,由于明确地不能再赋值 final 变量,有利于减小额外的同步开销,也能够省去一些防护性拷贝的必要。post
关于 final 也许会有性能的好处,不少文章或者书籍中都介绍了可在特定场景提升性能,好比,利用 final 可能有助于 JVM 将方法进行内联,能够改善编译器进行条件编译的能力等等。我在以前一篇文章进行了介绍,想了解的能够点击查阅。性能
扩展阅读:深刻理解 Java 中的 final 关键字this
在前面介绍了 final 在实践中的益处,须要注意的是,final 并不等同于 immutable,好比下面这段代码:
final List<String> strList = new ArrayList<>(); strList.add("wupx"); strList.add("huxy"); List<String> loveList = List.of("wupx", "huxy"); loveList.add("love");
final 只能约束 strList 这个引用不能够被赋值,可是 strList 对象行为不被 final 影响,添加元素等操做是彻底正常的。若是咱们真的但愿对象自己是不可变的,那么须要相应的类支持不可变的行为。在上面这个例子中,List.of 方法建立的自己就是不可变 List,最后那句 add 是会在运行时抛出异常的。
Immutable 在不少场景是很是棒的选择,某种意义上说,Java 语言目前并无原生的不可变支持,若是要实现 immutable 的类,咱们须要作到:
将 class 自身声明为 final,这样别人就不能扩展来绕过限制了。
将全部成员变量定义为 private 和 final,而且不要实现 setter 方法。
一般构造对象时,成员变量使用深度拷贝来初始化,而不是直接赋值,这是一种防护措施,由于你没法肯定输入对象不被其余人修改。
若是确实须要实现 getter 方法,或者其余可能会返回内部状态的方法,使用 copy-on-write 原则,建立私有的 copy。
关于 setter/getter 方法,不少人喜欢直接用 IDE 或者 Lombok 一次所有生成,建议最好肯定有须要时再实现。
对于 finally,知道怎么使用就足够了。须要关闭的链接等资源,更推荐使用 Java 7 中添加的 try-with-resources 语句,由于一般 Java 平台可以更好地处理异常状况,还能够减小代码量。
另外,有一些常被考到的 finally 问题。好比,下面代码会输出什么?
try { // do something System.exit(1); } finally{ System.out.println("Hello,I am finally。"); }
上面 finally 里面的代码是不会被执行的,由于 try-catch 异常退出了。
像其余 finally 中的代码不会执行的状况还有:
// 死循环 try{ while(ture){ System.out.println("always run"); } }finally{ System.out.println("ummm"); } // 线程被杀死 当执行 try-finally 的线程被杀死时,finally 中的代码也没法执行。
对于 finalize,是不推荐使用的,在 Java 9 中,已经将 Object.finalize() 标记为 deprecated。
为何呢?由于没法保证 finalize 何时执行,执行的是否符合预期。使用不当会影响性能,致使程序死锁、挂起等。
一般来讲,利用上面的提到的 try-with-resources 或者 try-finally 机制,是很是好的回收资源的办法。若是确实须要额外处理,能够考虑 Java 提供的 Cleaner 机制或者其余替代方法。
前面简单介绍了 finalize 是不推荐使用的,究竟为何不推荐使用呢?
所以对于消耗很是高频的资源,千万不要期望 finalize 去承担资源释放的主要职责。建议资源用完即显式释放,或者利用资源池来尽可能重用。
下面给出 finalize 掩盖资源回收时的出错信息的例子,让咱们来看 java.lang.ref.Finalizer 的源代码:
private void runFinalizer(JavaLangAccess jla) { // ... 省略部分代码 try { Object finalizee = this.get(); if (finalizee != null && !(finalizee instanceof java.lang.Enum)) { jla.invokeFinalize(finalizee); // Clear stack slot containing this variable, to decrease // the chances of false retention with a conservative GC finalizee = null; } } catch (Throwable x) { } super.clear(); }
看过以前讲解异常文章的朋友,应该能够很快看出 Throwable 是被吞掉的,也就意味着一旦出现异常或者出错,得不到任何有效信息。
扩展阅读:Java 异常处理的 20 个最佳实践,你知道几个?
Java 平台目前在逐步使用 java.lang.ref.Cleaner 来替换掉原有的 finalize 实现。Cleaner 的实现利用了幻象引用(PhantomReference),这是一种常见的所谓 post-mortem 清理机制。利用幻象引用和引用队列,能够保证对象被完全销毁前作一些相似资源回收的工做,好比关闭文件描述符(操做系统有限的资源),它比 finalize 更加轻量、更加可靠。
每一个 Cleaner 的操做都是独立的,有本身的运行线程,因此能够避免意外死锁等问题。
咱们能够为本身的模块构建一个 Cleaner,而后实现相应的清理逻辑,具体代码以下:
/** * Cleaner 是一个用于关闭资源的类,功能相似 finalize 方法 * Cleaner 有本身的线程,在全部清理操做完成后,本身会被 GC * 清理中抛出的异常会被忽略 * * 清理方法(一个 Runnable)只会运行一次。会在两种状况下运行: * 1. 注册的 Object 处于幻象引用状态 * 2. 显式调用 clean 方法 * * 经过幻象引用和引用队列实现 * 能够注册多个对象,一般被定义为静态(减小线程数量) * 注册对象后返回的Cleanable对象用于显式调用 clean 方法 * 实现清理行为的对象(下面的 state),不能拥有被清理对象的引用 * 若是将下面的 State 类改成非静态,第二个 CleaningExample 将不会被 clean, * 由于非静态内部类持有外部对象的引用,外部对象没法进入幻象引用状态 */ public class CleaningExample implements AutoCloseable { public static void main(String[] args) { try { // 使用JDK7的try with Resources显式调用clean方法 try (CleaningExample ignored = new CleaningExample()) { throw new RuntimeException(); } } catch (RuntimeException ignored) { } // 经过GC调用clean方法 new CleaningExample(); System.gc(); } private static final Cleaner CLEANER = Cleaner.create(); // 若是是非静态内部类,则会出错 static class State implements Runnable { State() { } @Override public void run() { System.out.println("Cleaning called"); } } private final State state; private final Cleaner.Cleanable cleanable; public CleaningExample() { this.state = new State(); this.cleanable = CLEANER.register(this, state); } @Override public void close() { cleanable.clean(); } }
其中,将 State 定义为 static,就是为了不普通的内部类隐含着对外部对象的强引用,由于那样会使外部对象没法进入幻象可达的状态。
从可预测性的角度来判断,Cleaner 或者幻象引用改善的程度仍然是有限的,若是因为种种缘由致使幻象引用堆积,一样会出现问题。因此,Cleaner 适合做为一种最后的保证手段,而不是彻底依赖 Cleaner 进行资源回收。
这篇文章首先从从语法角度分析了 final、finally、finalize,并从安全、性能、垃圾收集等方面逐步深刻,详细地讲解了 final、finally、finalize 三者的区别。
原文出处:https://www.cnblogs.com/wupeixuan/p/11756472.html