Java 中 final、finally、finalize 有什麼不一樣?這是在 Java 面試中常常問到的問題,他們究竟有什麼不一樣呢?java
這三個看起來很類似,其實他們的關係就像卡巴斯基和巴基斯坦同樣有基巴關係。面試
那麼若是被問到這個問題該怎麼回答呢?首先能夠從語法和使用角度出發簡單介紹三者的不一樣:編程
若是隻回答到這裏,就會沒有亮點,咱們能夠再深刻地去介紹三者的不一樣,好比從性能、併發、對象生命週期或垃圾收集基本過程等方面去談談本身的理解。安全
使用 final 關鍵字能夠明確表示代碼的語義、邏輯意圖,好比:併發
能夠將方法或者類聲明爲 final,這樣就能夠明確告知別人,這些行爲是不準修改的。Java 核心類庫的定義或源碼,好比 java.lang 包下面的不少類,至關一部分都被聲明成爲 final class,好比咱們常見的 String 類,在第三方類庫的一些基礎類中一樣如此,這能夠有效避免 API 使用者更改基礎功能,某種程度上,這是保證平臺安全的必要手段。ide
使用 final 修飾參數或者變量,也能夠清楚地避免意外賦值致使的編程錯誤,甚至,有人明確推薦將全部方法參數、本地變量、成員變量聲明成 final。post
final 變量產生了某種程度的不可變(immutable)的效果,因此,能夠用於保護只讀數據,尤爲是在併發編程中,由於明確地不能再賦值 final 變量,有利於減小額外的同步開銷,也能夠省去一些防護性拷貝的必要。性能
關於 final 也許會有性能的好處,不少文章或者書籍中都介紹了可在特定場景提升性能,好比,利用 final 可能有助於 JVM 將方法進行內聯,能夠改善編譯器進行條件編譯的能力等等。我在以前一篇文章進行了介紹,想了解的能夠點擊查閱。this
擴展閱讀:深刻理解 Java 中的 final 關鍵字spa
在前面介紹了 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,知道怎麼使用就足夠了。須要關閉的鏈接等資源,更推薦使用 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,是不推薦使用的,在 Java 9 中,已經將 Object.finalize() 標記爲 deprecated。
爲何呢?由於沒法保證 finalize 何時執行,執行的是否符合預期。使用不當會影響性能,致使程序死鎖、掛起等。
一般來講,利用上面的提到的 try-with-resources 或者 try-finally 機制,是很是好的回收資源的辦法。若是確實須要額外處理,能夠考慮 Java 提供的 Cleaner 機制或者其餘替代方法。
前面簡單介紹了 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 個最佳實踐,你知道幾個?
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 三者的區別。
本文由博客一文多發平臺 OpenWrite 發佈!