典型回答
final 可以用來修飾類、方法、變量,分別有不同的意義,final修飾class代表不可以繼承擴展,final修飾的變量不可以修改,而final的方法不可以重寫(override)
finally 則是 Java 保證重點代碼一定要被執(zhí)行的一種機制。我們可以使用 try-finally 或者 try-catch-finally 來進行類似關閉JDBC連接、unlock鎖等動作。
finalize 是基礎類 java.lang.Object的一個方法,它的設計目的是保證對象在被垃圾收集前完成特定資源的回收。finalize 機制現(xiàn)在已經(jīng)不推薦使用,并且在 JDK 9開始被標記為deprecated。因為無法保證 finalize 什么時候執(zhí)行,執(zhí)行的是否符合預期,使用不當會影響性能,導致程序死鎖、掛起等。
推薦使用 final 關鍵字來明確表示我們代碼的語義、邏輯意圖,這已經(jīng)被證明在很多場景下是非常好的實踐。
我們可以將方法或者類聲明為 final,這樣就可以明確告知別人,這些行為是不許修改的。
使用 final 修飾參數(shù)或者變量,也可以清楚地避免意外賦值導致的編程錯誤。
final 變量產(chǎn)生了某種程度的不可變(immutable)效果,可以用于保護只讀數(shù)據(jù),尤其是在并發(fā)編程中,因為明確地不能再賦值,能減少額外同步的開銷,省去一些防御性拷貝。
final 并不等同于 immutable
// final 只能約束 strList 這個引用不可以被賦值,strList對象的行為不受影響
final List<String> strList = new ArrayList<>();
strList.add("Hello");
strList.add("world");
// java9 創(chuàng)建immutable list的方式
List<String> unmodifiableStrList = List.of("hello", "world");
// 會拋出異常
unmodifiableStrList.add("again");
Java 語言目前并沒有原生的不可變支持,如果要實現(xiàn) immutable,我們需要做到:
- 將 class 自身聲明為 final,這樣別人就不能擴展來繞過限制
- 將所有成員變量定義為 private 和 final,并且不提供setter方法
- 通常構造對象時,成員變量使用深度拷貝來初始化,而不是直接賦值,這是一種防御措施,因為你無法確定輸入對象不被其他人修改。
- 如果確實需要實現(xiàn) getter 方法,或者其他可能會返回內部狀態(tài)的方法,,使用 copy-on-write 原則,創(chuàng)建私有的 copy。
finalize 真的那么不堪?
finalize 的執(zhí)行是和垃圾收集關聯(lián)在一起的,一旦實現(xiàn)了非空的finalize方法,就會導致相應對象回收呈現(xiàn)數(shù)量級上的變慢,有人統(tǒng)計過benchmark,大概是40、50倍的下降。
finalize 被設計成在對象被垃圾收集前調用,本質上成為了快速回收的阻礙者,可能導致你的對象經(jīng)過多個垃圾收集周期才被回收。實踐中,因為 finalize 拖慢垃圾收集,導致大量對象堆積,也是OOM產(chǎn)生的原因之一。
從另一個角度,我們要確保回收資源就是因為資源都是有限的,垃圾收集時間的不可預測,可能會極大加劇資源占用。這意味著對于消耗非常高頻的資源,千萬不要指望 finalize去承擔資源釋放的主要職責。
資源用完即顯式釋放,或者利用資源池來盡量重用。
有什么機制可以替換 finalize 嗎?
Java 平臺目前在逐步使用 java.lang.ref.Cleaner來替換掉原有的 finalize 實現(xiàn)。Cleaner 的實現(xiàn)利用了幻象引用(PhantomReference),這是一種常見的所謂 post-mortem 清理機制。
吸取了 finalize 里的教訓,每個 Cleaner 的操作都是獨立的,它有自己的運行線程,所以可以避免意外死鎖等問題。
實踐中,我們可以為自己的模塊構建一個 Cleaner,然后實現(xiàn)自己的清理邏輯。下面是 JDK 自身提供的樣例程序:
public class CleaningExample implements AutoCloseable {
// A cleaner, preferably one shared within a library
private static final Cleaner cleaner = <cleaner>;
static class State implements Runnable {
State(...) {
// initialize State needed for cleaning action
}
public void run() {
// cleanup action accessing State, executed at most once
}
}
private final State;
private final Cleaner.Cleanable cleanable
public CleaningExample() {
this.state = new State(...);
this.cleanable = cleaner.register(this, state);
}
public void close() {
cleanable.clean();
}
}