Java 中的 final、finally、finalize 有什麼不同?

来源:https://www.cnblogs.com/wupeixuan/archive/2019/10/29/11756472.html
-Advertisement-
Play Games

Java 中 final、finally、finalize 有什麼不同?這是在 Java 面試中經常問到的問題,他們究竟有什麼不同呢? 這三個看起來很相似,其實他們的關係就像卡巴斯基和巴基斯坦一樣有基巴關係。 那麼如果被問到這個問題該怎麼回答呢?首先可以從語法和使用角度出發簡單介紹三者的不同: fi ...


Java 中 final、finally、finalize 有什麼不同?這是在 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。

final 變數產生了某種程度的不可變(immutable)的效果,所以,可以用於保護只讀數據,尤其是在併發編程中,因為明確地不能再賦值 final 變數,有利於減少額外的同步開銷,也可以省去一些防禦性拷貝的必要。

關於 final 也許會有性能的好處,很多文章或者書籍中都介紹了可在特定場景提高性能,比如,利用 final 可能有助於 JVM 將方法進行內聯,可以改善編譯器進行條件編譯的能力等等。我在之前一篇文章進行了介紹,想瞭解的可以點擊查閱。

擴展閱讀:深入理解 Java 中的 final 關鍵字

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 大數據的發展歷史 3V:volume、velocity、variety(結構化和非結構化數據)、value(價值密度低) 大數據帶來的技術挑戰 存儲容量不斷增加 獲取有價值的信息的難度:搜索、廣告、推薦 大容量、多類型、高時效的數據處理場景,使得從數據中獲取有價值的信息變得非常困難 hadoop理論 ...
  • 集合: 集合就是一個容器,他可以存儲對象,我們說集合就是一個可變的數組 集合框架特點 1、list和set集合同時實現了collection介面 2、set集合存儲唯一,無序的對象。 3、list 存儲的不唯一,有序的對象(順序,按存儲的先後順序) 4、map介面存儲的方式:鍵值對進行存儲,鍵是唯一 ...
  • CDN概念 CDN全稱叫做“Content Delivery Network”,中文叫內容分髮網絡。 原理分析 我們知道,當我們使用功能變數名稱訪問某一個網站時,實際上就是將請求包(以Http請求為例)通過網路傳輸給某台伺服器,比如訪問“www.baidu.com”時: 首先解析出該功能變數名稱所對應的IP地址(D ...
  • 理解 首先委派模式不屬於23種設計模式。 所謂委派,個人理解是:將為達到最終結果的事情交給其他人或中間人來乾,我只要最終結果,其他的事情,由我委派的人來安排。 更直白的表達就是,比如,我們想要蓋一棟樓房,蓋完之後我要刷漆,這些事情我自己肯定不能做,所以我這時候就會去找一個“包工頭”來幫我完成這件事情 ...
  • "曦曦粉絲會視頻鏈接" : https://v.qq.com/x/page/o3010zxz73y.html 周末閑看微信時,看到了上面這個視頻,一個 4 歲的小孩,面對一群大人提出的人問題,回答得頭頭是道,燃起了我的好奇心:一個還沒上學的小孩竟然知道這麼多東西,是怎麼學會的?而且看起來小孩看起來很 ...
  • 用戶認證 auth模塊 django.contrib.auth中提供了許多方法,這裡主要介紹其中的三個: 1.1 、authenticate() 提供了用戶認證,即驗證用戶名以及密碼是否正確,一般需要username password兩個關鍵字參數 如果認證信息有效,會返回一個 User 對象。au ...
  • 萌新小白人生中的第一篇博客,難免會有差錯,還望各位大佬多多包涵。 1. Ajax技術簡介 Ajax(Asynchronous JavaScript and XML,非同步JavaScript和XML)時一種創建互動式網頁應用的網頁開發技術,它並不是一項新的技術,其產生的目的是用於實現頁面的局部刷新。通 ...
  • (原創)C/C++語言, 平臺是debian10, grpc版本:grpc c++/1.25.0 dev, 使用的例子是自帶的例子GreeterClient 創建 default executor 和 resolver executor 線程的主要流程,創建channel時會創建這2個線程 grpc ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...