淺談「final」、「finally」和「finalize」的區別

Programing Language:Java Basics

RICK
Aug 14, 2021

概要

這篇文章是拙作「Java 之常見面試考題解析」中的其中一個題目,由於筆者想更詳細的針對本題目探討,並礙於篇幅因素,故而另闢一文。

正文

雖然「final」、「finally」和「finalize」這些關鍵字看起來非常地相似,但實際上它們並沒有太大的關係,其就如同「狗」跟「熱狗」一般。

關鍵字:「final」

首先,我們先從比較基礎,但也相對重要的「final」說起。

簡言之,「final」是屬於一種「修飾詞綴」,它可以用來修飾「類別」、「方法」或是「變數」,但必須注意的是,其會因被修飾對象不同而代表不同的意思,例如當「final」用於修飾「類別」對象時,則代表該類別「不能被繼承」,而若是用於修飾「方法」時,則是代表該方法「不能被覆寫」。

但最常見的還是用於修飾「變數」,包含「欄位」與「區域變數」,其所代表的意思是該變數「不能再被重新賦值」;但要特別注意的是,當「變數」被「final」修飾後,該變數就不能夠再被賦值,所以該變數必須要在使用前先被初始化,否則就會編譯上的錯誤,如下:

但並不一定要在宣告時就立刻初始化,只要在變數使用之前初始化即可,如下:

關於「final」的使用方式的介紹大概就到這,其它更詳細的介紹可以參考良葛格的「再看 final 關鍵字」。

但在介紹下一個關鍵字之前,筆者還想藉此機會討論一下「final」的用處;事實上,在實際撰寫程式碼的時候,「final」更多是其所代表的「語意」,當「final」加在「類別」或「方法」上時,其意義代表告訴其它開發者,不要再對該「類別」或「方法」進行繼承或覆寫;這樣一來,可以「避免類別內容」或「方法實作」被不預期的改動,符合封裝原則,是具有安全性的手法。

至於在「變數」方面的概念也是如此,因為「final」賦予「變數」某種程度上的「不可改變」特性,所以非常適合用於「Read Only Data」上,如某些傳入參數、常數參數等;同時該特性也省去了「維護同步」的必要。

事實上,上述關於「final」的特性也體現在一些較新的程式語言中,譬如在「Kotlin」中,變數的宣告僅能為「var」或「val」,「var」就是一般變數的宣告方式,而「val」就像是「final var」,此外,「類別」與「方法」也有同樣的設計,其預設改成無法被「繼承」與被「覆寫」,倘若有「繼承」或「覆寫」的需求時,則需加上關鍵字「opne」進行修飾。

總的來說,撰寫程式碼時,「final」雖非必要,但考量程式碼的「可讀性」與「可維護性」的因素,例如輔助 IDE 的語法錯誤提示⋯等,在撰寫程式碼時盡量多輔以「final」修飾是個良好的習慣,甚少有不少開發團隊將此要求列為團隊中「程式碼撰寫規範」的的準則之一。

但「final」除了提高程式碼的「可讀性」外,就真的沒有其它實際上的功能嗎?

或許讀者們曾聽過的其中一個原因是多用「final」修飾可以提升程式效能,理由是因為「final」限制了彈性,因此能降低資源損耗;關於這點,筆者認為其這樣的說法是有些不完整的。

事實上,關於這個問題,在「StackOverFlow」是有討論串的,詳見「Does use of final keyword in Java improve the performance?」與「Do java finals help the compiler create more efficient bytecode?」。

筆者總結一下討論串的結論,該問題的答案會取決於「編譯器」,但以現在編譯優化的技術而言,「final」修飾對於提升程式效能的幫助即便是有,也是微乎其微。

關鍵字:「finally

然後是「finally」,詳細內容可以參考官方文件,如下:

它的功能是用於「保證特定的程式片段一定會被執行」的一種機制,必須搭配「try」語法使用;多用於善後、清理的機制,例如執行「關閉已開啟的資源」。

但隨著「try-with-resources」機制的出現,基本上這個關鍵字幾乎就已經不再被使用了;就目前而言,仍以「Java」為主要開發語言不外乎是「後端」與「Android」,前者對於資源的控管更多仰賴「框架」,而「Android」則是有「Android SDK」提供的專屬 API。

關於「finally」還有個小知識,事實上,我們可以將由「finally」所包覆的區塊理解為,不論如何只要程式還在處於「運行階段」都會被執行的區塊,因此,即使是拋「Exception」或呼叫「return」後,該區塊都仍會被執行;但若是程式並非處於「運行階段」,那麼「finally」就可能會出現不被執行的情況,例如「System.exit()」。

關鍵字:「finalize」

最後是「finalize」,我們稱之為「解構函式」,談到「Java」解構就幾乎無法避免的要談到「GC 機制」。

創建物件,我們稱之為「建構」,那「解構」顧名思義就是分解物件,聽起來很合理,有「建」有「解」,所以物件導向的程式開發人員,在操作物件時不僅需要控制物件的「建構」,同時也要控制物件的「解構」,這聽起來很合理,或許在某些程式語言,這樣並沒有錯,但是很遺憾的,在「Java」中並不是這樣的,在「Java」中,物件的生命週期也是從「建構」開始,也的確是由程式開發人員控制,藉由「new」這個關鍵字,但結束就不是由程式開發人員控制,而是由「GC」決定。

然而關於「finalize」的機制與使用方式,可以參考良葛格的「惡名昭彰的 finalize」;筆者並不打算於此過多著墨,畢竟「Java」的官方是不預期我們去使用、修改解構函式的,應避免更動它或與它相關的機制,以防止造成不必要的錯誤,如死結⋯等,詳細資訊可以參考「The Secret Life Of The Finalizer」。

總結

上述內容介紹了關於「final」、「finally」和「finalize」這三個關鍵字的知識點,但說實在的,除了「final」之外,其它其實都已經不是那麼常的被使用,尤其是「finalize」,只要大概了解個概念即可。

--

--

RICK
RICK

Written by RICK

當遇到重開機無法解決的 BUG 時,那就試試關機吧。

No responses yet