1. 程式人生 > >看完這篇 final、finally 和 finalize 和麵試官扯皮就沒問題了

看完這篇 final、finally 和 finalize 和麵試官扯皮就沒問題了

> 我把自己以往的文章彙總成為了 Github ,歡迎各位大佬 star > https://github.com/crisxuan/bestJavaer > 已提交此篇文章 `final` 是 Java 中的關鍵字,它也是 Java 中很重要的一個關鍵字,final 修飾的類、方法、變數有不同的含義;`finally` 也是一個關鍵字,不過我們可以使用 finally 和其他關鍵字結合做一些組合操作; `finalize` 是一個不讓人待見的方法,它是物件祖宗 `Object` 中的一個方法,finalize 機制現在已經不推薦使用了。本篇文章,cxuan 就帶你從這三個關鍵字入手,帶你從用法、應用、原理的角度帶你深入淺出理解這三個關鍵字。 ## final、finally 和 finalize 我相信在座的各位都是資深程式設計師,final 這種基礎關鍵字就不用多說了。不過,還是要照顧一下小白讀者,畢竟我們都是從小白走過來的嘛。 ### final 修飾類、屬性和方法 `final` 可以用來修飾類,final 修飾的類不允許其他類繼承,也就是說,final 修飾的類是獨一無二的。如下所示
我們首先定義了一個 FinalUsage 類,它使用 final 修飾,同時我們又定義了一個 FinalUsageExtend 類,它想要`繼承(extend)` FinalUsage,我們如上繼承後,編譯器不讓我們這麼玩兒,它提示我們 **不能從 FinalUsage** 類繼承,為什麼呢?不用管,這是 Java 的約定,有一些為什麼沒有必要,遵守就行。 `final` 可以用來修飾方法,final 修飾的方法不允許被重寫,我們先演示一下不用 final 關鍵字修飾的情況 如上圖所示,我們使用 FinalUsageExtend 類繼承了 FinalUsage 類,並提供了 `writeArticle` 方法的重寫。這樣編譯是沒有問題的,重寫的關鍵點是 `@Override` 註解和方法修飾符、名稱、返回值的一致性。 >
注意:很多程式設計師在重寫方法的時候都會忽略 @Override,這樣其實無疑增加了程式碼閱讀的難度,不建議這樣。 當我們使用 final 修飾方法後,這個方法則不能被重寫,如下所示 當我們把 writeArticle 方法宣告為 void 後,重寫的方法會報錯,無法重寫 writeArticle 方法。 `final` 可以修飾變數,final 修飾的變數一經定義後就不能被修改,如下所示 編譯器提示的錯誤正是不能繼承一個被 final 修飾的類。 我們上面使用的是字串 String ,String 預設就是 final 的,其實用不用 final 修飾意義不大,因為字串本來就不能被改寫,這並不能說明問題。 我們改寫一下,使用基本資料型別來演示
同樣的可以看到,編譯器仍然給出了 age 不能被改寫的提示,由此可以證明,final 修飾的變數不能被重寫。 在 Java 中不僅僅只有基本資料型別,還有引用資料型別,那麼引用型別被 final 修飾後會如何呢?我們看一下下面的程式碼 首先構造一個 `Person` 類 ```java public class Person { int id; String name; get() and set() ... toString()... } ``` 然後我們定義一個 final 的 Person 變數。 ```java static final Person person = new Person(25,"cxuan"); public static void main(String[] args) { System.out.println(person); person.setId(26); person.setName("cxuan001"); System.out.println(person); } ``` 輸出一下,你會發現一個奇怪的現象,為什麼我們明明改了 person 中的 id 和 name ,編譯器卻沒有報錯呢? 這是因為,final 修飾的引用型別,只是保證物件的引用不會改變。物件內部的資料可以改變。這就涉及到物件在記憶體中的分配問題,我們後面再說。 ### finally 保證程式一定被執行 `finally` 是保證程式一定執行的機制,同樣的它也是 Java 中的一個關鍵字,一般來講,finally 一般不會單獨使用,它一般和 try 塊一起使用,例如下面是一段 try...finally 程式碼塊 ```java try{ lock.lock(); }finally { lock.unlock(); } ``` 這是一段加鎖/解鎖的程式碼示例,在 lock 加鎖後,在 finally 中執行解鎖操作,因為 finally 能夠保證程式碼一定被執行,所以一般都會把一些比較重要的程式碼放在 finally 中,例如解鎖操作、流關閉操作、連線釋放操作等。 當 lock.lock() 產生異常時還可以和 `try...catch...finally` 一起使用 ```java try{ lock.lock(); }catch(Exception e){ e.printStackTrace(); }finally { lock.unlock(); } ``` try...finally 這種寫法適用於 JDK1.7 之前,在 JDK1.7 中引入了一種新的關閉流的操作,那就是 `try...with...resources`,Java 引入了 try-with-resources 宣告,將 try-catch-finally 簡化為 try-catch,這其實是一種語法糖,並不是多了一種語法。try...with...resources 在編譯時還是會進行轉化為 try-catch-finally 語句。 >`語法糖(Syntactic sugar)`,也譯為糖衣語法,是指計算機語言中新增的某種語法,這種語法對語言的功能並沒有影響,但是更方便程式設計師使用。通常來說使用語法糖能夠增加程式的可讀性,從而減少程式程式碼出錯的機會。 在 Java 中,有一些為了簡化程式設計師使用的語法糖,後面有機會我們再談。 ### finalize 的作用 finalize 是祖宗類 `Object`類的一個方法,它的設計目的是保證物件在垃圾收集前完成特定資源的回收。finalize 現在已經不再推薦使用,在 JDK 1.9 中已經明確的被標記為 `deprecated`。 ## 深入理解 final 、finally 和 finalize ### final 設計 許多程式語言都會有某種方法來告知編譯器,某一塊資料是恆定不變的。有時候恆定不變的資料很有用,比如 * 一個永不改變的編譯期常量 。例如 **static final int num = 1024** * 一個執行時被初始化的值,而且你不希望改變它 final 的設計會和 `abstract` 的設計產生衝突,因為 abstract 關鍵字主要修飾`抽象類`,而抽象類需要被具體的類所實現。final 表示禁止繼承,也就不會存在被實現的問題。因為只有繼承後,子類才可以實現父類的方法。 類中的所有 `private` 都隱式的指定為 `final` 的,在 private 修飾的程式碼中使用 final 並沒有額外的意義。 #### 空白 final Java 是允許`空白 final` 的,空白 final 指的是宣告為 final ,但是卻沒有對其賦值使其初始化。但是無論如何,編譯器都需要初始化 final,所以這個初始化的任務就交給了`構造器`來完成,空白 final 給 final 提供了更大的靈活性。如下程式碼 ```java public class FinalTest { final Integer finalNum; public FinalTest(){ finalNum = 11; } public FinalTest(int num){ finalNum = num; } public static void main(String[] args) { new FinalTest(); new FinalTest(25); } } ``` 在不同的構造器中對不同的 final 進行初始化,使 finalNum 的使用更加靈活。 使用 final 的方法主要有兩個:`不可變` 和 `效率` * 不可變:不可變說的是把方法鎖定(注意不是加鎖),重在防止其他方法重寫。 * 效率:這個主要是針對 Java 早期版本來說的,在 Java 早期實現中,如果將方法宣告為 final 的,就是同意編譯器將對此方法的呼叫改為`內嵌呼叫`,但是卻沒有帶來顯著的效能優化。這種呼叫就比較雞肋,在 Java5/6 中,hotspot 虛擬機器會自動探測到內嵌呼叫,並把它們優化掉,所以使用 final 修飾的方法就主要有一個:不可變。 >注意:final 不是 Immutable 的,Immutable 才是真正的不可變。 final 不是真正的 `Immutable`,因為 final 關鍵字引用的物件是可以改變的。如果我們真的希望物件不可變,通常需要相應的類支援不可變行為,比如下面這段程式碼 ```java final List fList = new ArrayList(); fList.add("Hello"); fList.add("World"); List unmodfiableList = List.of("hello","world"); unmodfiableList.add("again"); ``` `List.of` 方法建立的就是不可變的 List。不可變 Immutable 在很多情況下是很好的選擇,一般來說,實現 Immutable 需要注意如下幾點 * 將類宣告為 final,防止其他類進行擴充套件。 * 將類內部的成員變數(包括例項變數和類變數)宣告為 `private` 或 `final` 的,不要提供可以修改成員變數的方法,也就是 setter 方法。 * 在構造物件時,通常使用 `deep-clone` ,這樣有助於防止在直接對物件賦值時,其他人對輸入物件的修改。 * 堅持 `copy-on-write` 原則,建立私有的拷貝。 ### final 能提高效能嗎? final 能否提高效能一直是業界爭論的點,很多