天天看點

Java 中的 final、finally、finalize 有什麼不同?finalfinal 與 immutablefinallyfinalize為什麼不推薦使用 finalize?有更好的方法替代 finalize 嗎?總結

java 中的 final、finally、finalize 有什麼不同?這是在 java 面試中經常問到的問題,他們究竟有什麼不同呢?

這三個看起來很相似,其實他們的關系就像卡巴斯基和巴基斯坦一樣有基巴關系。

那麼如果被問到這個問題該怎麼回答呢?首先可以從文法和使用角度出發簡單介紹三者的不同:

•final 可以用來修飾類、方法、變量,分别有不同的意義,final 修飾的 class 代表不可以繼承擴充,比如 string 類;final 的變量是不可以修改的;java 裡用 final 修飾符去修飾一個方法的唯一正确用途就是表達:這個方法原本是一個虛方法,現在通過 final 來聲明這個方法不允許在派生類中進一步被覆寫(override)。

•finally 是 java 保證重點代碼一定要被執行的一種機制。可以使用 try-finally 或者 try-catch-finally 來進行關閉資源、保證 unlock 鎖等動作。

•finalize 是基礎類 java.lang.object 的一個方法,設計目的是保證對象在被垃圾收集前完成特定資源的回收。finalize 機制現在已經不推薦使用,并且在 jdk 9 開始被标記為 deprecated。

如果隻回答到這裡,就會沒有亮點,我們可以再深入地去介紹三者的不同。

使用 final 關鍵字可以明确表示代碼的語義、邏輯意圖,比如:

可以将方法或者類聲明為 final,這樣就可以明确告知别人,這些行為是不許修改的。java 核心類庫的定義或源碼,比如 java.lang 包下面的很多類,相當一部分都被聲明成為 final class,比如我們常見的 string 類,在第三方類庫的一些基礎類中同樣如此,這可以有效避免 api 使用者更改基礎功能,某種程度上,這是保證平台安全的必要手段。

使用 final 修飾參數或者變量,也可以清楚地避免意外指派導緻的程式設計錯誤,甚至,有人明确推薦将所有方法參數、本地變量、成員變量聲明成 final。

final 變量産生了某種程度的不可變(immutable)的效果,是以,可以用于保護隻讀資料,尤其是在并發程式設計中,因為明确地不能再指派 final 變量,有利于減少額外的同步開銷,也可以省去一些防禦性拷貝的必要。

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

擴充閱讀:深入了解 java 中的 final 關鍵字

在前面介紹了 final 在實踐中的益處,需要注意的是,final 并不等同于 immutable,比如下面這段代碼:

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 問題。比如,下面代碼會輸出什麼?

上面 finally 裡面的代碼是不會被執行的,因為 try-catch 異常退出了。

像其他 finally 中的代碼不會執行的情況還有:

對于 finalize,是不推薦使用的,在 java 9 中,已經将 object.finalize() 标記為 deprecated。

為什麼呢?因為無法保證 finalize 什麼時候執行,執行的是否符合預期。使用不當會影響性能,導緻程式死鎖、挂起等。

通常來說,利用上面的提到的 try-with-resources 或者 try-finally 機制,是非常好的回收資源的辦法。如果确實需要額外處理,可以考慮 java 提供的 cleaner 機制或者其他替代方法。

前面簡單介紹了 finalize 是不推薦使用的,究竟為什麼不推薦使用呢?

1.finalize 的執行是和垃圾收集關聯在一起的,一旦實作了非空的 finalize 方法,就會導緻相應對象回收呈現數量級上的變慢。

2.finalize 被設計成在對象被垃圾收集前調用,jvm 要對它進行額外處理。finalize 本質上成為了快速回收的阻礙者,可能導緻對象經過多個垃圾收集周期才能被回收。

3.finalize 拖慢垃圾收集,導緻大量對象堆積,也是一種典型的導緻 oom 的原因。

4.要確定回收資源就是因為資源都是有限的,垃圾收集時間的不可預測,可能會極大加劇資源占用。

5.finalize 會掩蓋資源回收時的出錯資訊。

是以對于消耗非常高頻的資源,千萬不要指望 finalize 去承擔資源釋放的主要職責。建議資源用完即顯式釋放,或者利用資源池來盡量重用。

下面給出 finalize 掩蓋資源回收時的出錯資訊的例子,讓我們來看 java.lang.ref.finalizer 的源代碼:

看過之前講解異常文章的朋友,應該可以很快看出 throwable 是被吞掉的,也就意味着一旦出現異常或者出錯,得不到任何有效資訊。

擴充閱讀:java 異常處理的 20 個最佳實踐,你知道幾個?

java 平台目前在逐漸使用 java.lang.ref.cleaner 來替換掉原有的 finalize 實作。cleaner 的實作利用了幻象引用(phantomreference),這是一種常見的所謂 post-mortem 清理機制。利用幻象引用和引用隊列,可以保證對象被徹底銷毀前做一些類似資源回收的工作,比如關閉檔案描述符(作業系統有限的資源),它比 finalize 更加輕量、更加可靠。

每個 cleaner 的操作都是獨立的,有自己的運作線程,是以可以避免意外死鎖等問題。

我們可以為自己的子產品建構一個 cleaner,然後實作相應的清理邏輯,具體代碼如下:

其中,将 state 定義為 static,就是為了避免普通的内部類隐含着對外部對象的強引用,因為那樣會使外部對象無法進入幻象可達的狀态。

從可預測性的角度來判斷,cleaner 或者幻象引用改善的程度仍然是有限的,如果由于種種原因導緻幻象引用堆積,同樣會出現問題。是以,cleaner 适合作為一種最後的保證手段,而不是完全依賴 cleaner 進行資源回收。

這篇文章首先從從文法角度分析了 final、finally、finalize,并從安全、性能、垃圾收集等方面逐漸深入,詳細地講解了 final、finally、finalize 三者的差別。

參考 https://blog.csdn.net/qq_27276045/article/details/102774119 https://blog.csdn.net/qq_27276045/article/details/102762241 https://time.geekbang.org/column/article/6906

●深入了解 java 中的 final 關鍵字

●如何定制 spring boot 的 banner?

●java 異常處理的 20 個最佳實踐,你知道幾個?

●mysql 日志系統之 redo log 和 binlog

●java中set集合是如何實作添加元素保證不重複的?

●為什麼不建議使用date,而是使用java8新的時間和日期api?

●spring boot 定時任務 @scheduled

●如何選擇消息隊列?

●初探微服務架構

●從單體應用走向服務化

●什麼是微服務?

●一條sql查詢語句是如何執行的?

Java 中的 final、finally、finalize 有什麼不同?finalfinal 與 immutablefinallyfinalize為什麼不推薦使用 finalize?有更好的方法替代 finalize 嗎?總結

武培軒

有幫助?在看,轉發走一波

喜歡作者