Java 中的 final、finally、finalize 有什麼不同?
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 是不推薦使用的,究竟為什麼不推薦使用呢?
- finalize 的執行是和垃圾收集關聯在一起的,一旦實現了非空的 finalize 方法,就會導致相應物件回收呈現數量級上的變慢。
- finalize 被設計成在物件被垃圾收集前呼叫,JVM 要對它進行額外處理。finalize 本質上成為了快速回收的阻礙者,可能導致物件經過多個垃圾收集週期才能被回收。
- finalize 拖慢垃圾收集,導致大量物件堆積,也是一種典型的導致 OOM 的原因。
- 要確保回收資源就是因為資源都是有限的,垃圾收集時間的不可預測,可能會極大加劇資源佔用。
- 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 三者的區別。
相關推薦
Java中 try--catch-- finally、throw、throws 的用法
一、try {..} catch {..}finally {..}用法 try { 執行的程式碼,其中可能有異常。一旦發現異常,則立即跳到catch執行。否則不會執行catch裡面的內容 } catch (Exception e) { 除非try裡面執行程式碼發生了異常,否則這裡的程式碼不會執行 }
Java中final、finally、finalize有什麽區別?
ava 部分 控制 重寫 最終 垃圾回收 final 釋放資源 特殊情況 final、finally和finalize的區別是什麽? final: 最終的意思,可以修飾類,方法和變量。 它修飾的類,不能被繼承 它修飾的方法,不能被重寫 它修飾的變量,不能被改變 fin
Java 中的 final、finally、finalize 有什麼不同?
Java 中 final、finally、finalize 有什麼不同?這是在 Java 面試中經常問到的問題,他們究竟有什麼不同呢? 這三個看起來很相似,其實他們的關係就像卡巴斯基和巴基斯坦一樣有基巴關係。 那麼如果被問到這個問題該怎麼回答呢?首先可以從語法和使用角度出發簡單介紹三者的不同: final
Java中final、finally、finalize的區別和用法
1.簡單區別 final用於宣告屬性,方法和類,分別表示屬性不可交變,方法不可覆蓋,類不可繼承。 finally是異常處理語句結構的一部分,表示總是執行。 finalize是Object類的一個方法,在垃圾收集器執行的時候會呼叫被回收物件的此方法,供垃圾收集時的其他資源回收,例
Java中final、finally、finalize的區別
1.2 定義方法當final用來定義一個方法時,會有什麼效果呢?正如大家所知,它表示這個方法不可以被子類重寫,但是它這不影響它被子類繼承。我們寫段程式碼來驗證一下: Java程式碼public class ParentClass { public final void TestFinal() {
Java中final、finally和finalize的區別
final、finally、finalize的區別 1、final修飾符(關鍵字)。被final修飾的類,就意味著不能再派生出新的子類,不能作為父類而被子類繼承。因此一個類不能既被abstract宣告
Java中final、finally 和 finalize 的區別
1.final:Java中的修飾符、關鍵字 final是java中的修飾符,用於修飾屬性(變數)、方法、類。代表屬性值不可修改、方法不可覆蓋、類不可繼承。 當變數被宣告final時,必須要給定初值,而在以後的引用中只能讀取,不能修改。 例項: 上圖我們定義了final 字
Java核心-03 談談final、finally、 finalize有什麽不同?
推薦 垃圾 源碼 私有 pri jdk 收集 hotspot 減少 今天,我要問你的是一個經典的 Java 基礎題目,談談 final、finally、 finalize 有什麽不同? 典型回答 final 可以用來修飾類、方法、變量,分別有不同地意義,final修飾
final、finally、finalize有什麼不同
final可以用來修飾類、方法、變數,分別有不同的意義,final修飾的類代表不可以繼承擴充套件,final的變數是不可以修改的,而final的方法也是不可以重寫的。 finally則
第三講 談談final、finally、finalize有什麼不同?
final 可以用來修飾類、方法、變數,分別有不同的意義,nal 修飾的 class 代表不可以繼承擴充套件,final 的變數是不可以修改的,而 final 的方法也是不可以重寫的(override)。 finally 則是 Java 保證重點程式碼一定要被執
java final 、finally與finalize的區別
final:在java中,final一般是指不可變的,是一個修飾符,可以修飾常量、方法、類, public class TestOne{ final int MAX_ONE=1; public void test(){ MAX_ONE=2;//在這裡是錯誤的,變數被final修飾後不能再
三、final、finally、 finalize有什麼不同?
一、final 不可變 通常用來修飾一個類或者一個方法或者一個變數 1.修飾類的時候,表示這個類是不可以被繼承的。(避免重寫方法,更改一些功能) 2.修飾方法的時候,表示這個方法是不可以被重寫的。(同上) 3.修飾變數的時候,表示這個變數的引用是不可以更改的。(保證變
final、finally、 finalize有什麼不同?
final 中文翻譯:最終的;決定性的;不可更改的,可以用來修飾類、方法、變數,分別有不同的意義 修飾類:當用final修飾一個類時,表明這個類不能被繼承。也就是說,這個類不能其他類繼承(反向說如果不想讓這個類被繼承就使用final進行修飾類)。final類中的成員變數可以根
java裡final、finally、finalize的區別
final :java 關鍵字。被final修飾的變數不可進行值更改,必須在定義時一併初始化。如final int i=1,則下面對i只能使用,而不能進行更改如i++,更改必定會報錯。同理,final修飾方法時,則子類不能對該方法進行重寫;被final修飾的類不允許繼承。所以
final、finally、finalize分別有什麼作用
1、final可以修飾變數、方法、類 final修飾變量表示這個變數就是個常量;(final修飾基本資料型別時,表示這個變數是一個常量,final修飾一個物件時表示這個物件的引用是不可修改的,但物件的
Java基礎總結從0開始(二):final、finally、finalize的區別
final:用於修飾類、方法和屬性;被修飾的類不能被繼承,方法不能重寫,屬性不可改變即參量; -----ps:abstract和final不能同時修飾類finally:多用捕獲異常後必須執行執行的程式碼塊,比例關閉連線,IO流等;finaliza:JVM在回收垃
java面試之Final、finally、finalize區別
1、final Final可以用於成員變數(包括方法引數),方法、類。 Final成員 作為變數 變數一旦被初始化便不可改變(對於基本型別,指的是值不變;對於物件型別,指的是引用不變),初始化只可能在兩個地方:定義處和建構函式。 作為方法引數 對於基本型別,定義成fi
Java核心技術36講 第三講:final、finally、finalize的不同
final、finally、finalize final可以用來修飾類、方法、變數,分別有不同的意義, final修飾的class不可以繼承擴充套件,final的變數不可以被修改,final的方法不
JAVA面試題解惑系列 – final、finally和finalize的區別
這是一道再經典不過的面試題了,我們在各個公司的面試題中幾乎都能看到它的身影。 final、finally和finalize雖然長得像孿生三兄弟一樣,但是它們的含義和用法卻是大相徑庭。 這一次我們就一起來回顧一下這方面的知識。 final關鍵字 我們首先來說說final。它可以用於以下四個地方: 1. 定義變