Java 中的 final、finally、finalize 有什么不一样?

Java 中 final、finally、finalize 有什么不一样?这是在 Java 面试中常常问到的问题,他们究竟有什么不一样呢?html

这三个看起来很类似,其实他们的关系就像卡巴斯基和巴基斯坦同样有基巴关系。java

那么若是被问到这个问题该怎么回答呢?首先能够从语法和使用角度出发简单介绍三者的不一样:面试

  • final 能够用来修饰类、方法、变量,分别有不一样的意义,final 修饰的 class 表明不能够继承扩展,final 的变量是不能够修改的,而 final 的方法也是不能够重写的(override)。
  • finally 是 Java 保证重点代码必定要被执行的一种机制。可使用 try-finally 或者 try-catch-finally 来进行相似关闭 JDBC 链接、保证 unlock 锁等动做。
  • finalize 是基础类 java.lang.Object 的一个方法,设计目的是保证对象在被垃圾收集前完成特定资源的回收。finalize 机制如今已经不推荐使用,而且在 JDK 9 开始被标记为 deprecated。

若是只回答到这里,就会没有亮点,咱们能够再深刻地去介绍三者的不一样,好比从性能、并发、对象生命周期或垃圾收集基本过程等方面去谈谈本身的理解。编程

final

使用 final 关键字能够明确表示代码的语义、逻辑意图,好比:安全

能够将方法或者类声明为 final,这样就能够明确告知别人,这些行为是不准修改的。 Java 核心类库的定义或源码,好比 java.lang 包下面的不少类,至关一部分都被声明成为 final class,好比咱们常见的 String 类,在第三方类库的一些基础类中一样如此,这能够有效避免 API 使用者更改基础功能,某种程度上,这是保证平台安全的必要手段。并发

使用 final 修饰参数或者变量,也能够清楚地避免意外赋值致使的编程错误,甚至,有人明确推荐将全部方法参数、本地变量、成员变量声明成 final。ide

final 变量产生了某种程度的不可变(immutable)的效果,因此,能够用于保护只读数据,尤为是在并发编程中,由于明确地不能再赋值 final 变量,有利于减小额外的同步开销,也能够省去一些防护性拷贝的必要。post

关于 final 也许会有性能的好处,不少文章或者书籍中都介绍了可在特定场景提升性能,好比,利用 final 可能有助于 JVM 将方法进行内联,能够改善编译器进行条件编译的能力等等。我在以前一篇文章进行了介绍,想了解的能够点击查阅。性能

扩展阅读:深刻理解 Java 中的 final 关键字this

final 与 immutable

在前面介绍了 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

对于 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

对于 finalize,是不推荐使用的,在 Java 9 中,已经将 Object.finalize() 标记为 deprecated。

为何呢?由于没法保证 finalize 何时执行,执行的是否符合预期。使用不当会影响性能,致使程序死锁、挂起等。

一般来讲,利用上面的提到的 try-with-resources 或者 try-finally 机制,是很是好的回收资源的办法。若是确实须要额外处理,能够考虑 Java 提供的 Cleaner 机制或者其余替代方法。

为何不推荐使用 finalize?

前面简单介绍了 finalize 是不推荐使用的,究竟为何不推荐使用呢?

  1. finalize 的执行是和垃圾收集关联在一块儿的,一旦实现了非空的 finalize 方法,就会致使相应对象回收呈现数量级上的变慢。
  2. finalize 被设计成在对象被垃圾收集前调用,JVM 要对它进行额外处理。finalize 本质上成为了快速回收的阻碍者,可能致使对象通过多个垃圾收集周期才能被回收。
  3. finalize 拖慢垃圾收集,致使大量对象堆积,也是一种典型的致使 OOM 的缘由。
  4. 要确保回收资源就是由于资源都是有限的,垃圾收集时间的不可预测,可能会极大加重资源占用。
  5. 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 个最佳实践,你知道几个?

有更好的方法替代 finalize 吗?

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 三者的区别。 file

原文出处:https://www.cnblogs.com/wupeixuan/p/11756472.html

相关文章
相关标签/搜索