典型回答:
final 可以用來修飾可變類占调、方法移剪、對象纵苛,分別有不同的意義,final 修飾的 class 代表不可以繼承擴展取试,final 的變量是不可以修改的瞬浓,而 final 的方法是不可以重寫的猿棉。
finally 則是 Java 保證重點代碼一定要被執(zhí)行的一種機制屑咳。我們可以使用 try-finally 或者 try-catch-finally 來進行類似關閉 JDBC 連接兆龙、保證 unlock 鎖等動作紫皇。
finalize 是基礎類 java.lang.Object 的一個方法坝橡,它的設計目的是保證對象在被垃圾收集前完成特定的資源回收计寇。finalize 機制現(xiàn)在已經(jīng)不推薦使用,并且在 JDK 9 起開始被標記為 deprecated。
考點分析
上面的回答主要是從概念的角度出發(fā)的蝶押,其實還可以從很多方面再深入探討,講講對性能苇侵、并發(fā)榆浓、對象生命周期或垃圾收集基本過程等方面的理解撕攒。
推薦使用 final 關鍵字來明確表示我們代碼的語義抖坪、邏輯意圖擦俐,這已經(jīng)被證明在很多場景下是非常好的實踐捌肴,例如:
- 我們可以將方法或者類聲明為 final藕咏,這樣就可以明確告知別人饥悴,這些行為是不可修改的西设。
- 使用 final 修飾參數(shù)或者變量贷揽,也可以清楚地避免意外賦值導致的編程錯誤梦碗,甚至印屁,有人明確推薦將所有的方法參數(shù)雄人、本地變量念赶、成員變量聲明成 final珍坊。
- final 產(chǎn)生了某種程度的不可變 (immutable) 的效果阵漏,所以履怯,可以用來保護只讀數(shù)據(jù)叹洲,尤其是在并發(fā)編程中运提,因為明確地不能再賦值 final 變量民泵,有利于減少同步開銷栈妆,也可以省去一些防御性拷貝地必要鳞尔。
總結起來就是:聲明 final 關鍵字市框,從語義上告訴我們自己和閱讀枫振、合作一起編程的人,這是一個不可變對象乒疏,讓大家心中有底窍侧。同時 final 關鍵字的修飾硼啤,可以避免我們自己不小心的意外賦值谴返,也可以保護只讀數(shù)據(jù),減少防御性拷貝的必要渠抹,避免別人意外賦值。
對于finally,我認為明確知道怎么使用就夠了三椿,推薦使用 Java 7 中添加的 try-with-resources 語句伴郁,因為通常 Java 平臺能夠更好地處理異常情況剂陡,編碼量也要少很多鸭栖,何樂而不為呢。
另外可以注意下 finally 一些特殊的不會被執(zhí)行的情況。比如
- System.exit(1)
try {
// do something
System.exit(1);
} finally{
System.out.println(“Print from finally”);
}
- 死循環(huán)
try {
while (true) {
System.out.println("hello world");
}
} finally {
System.out.println("hello world");
}
- 線程被殺死
當執(zhí)行 try-finally 的線程被殺死時屑墨。finally 也無法執(zhí)行。
對于 finalize卵史,我們要明確它是不被推薦使用的灿里,在業(yè)界的實踐證明中,一再證明它不是個好的辦法程腹,在 Java 9 中匣吊,甚至將 Object.finalize() 標記為 deprecated!如果沒有特別的原因寸潦,不要實現(xiàn) finalize 方法色鸳,也不要指望利用它來進行資源回收。
簡單說见转,我們無法保證 finalize 什么時候執(zhí)行命雀,執(zhí)行是否符合預期。使用不當會影響性能斩箫,導致程序死鎖梧油、掛起等。
如果要實現(xiàn)資源回收甘晤,推薦使用 try-with-resources 或者 try-finally 機制撮弧。如果需要額外處理,可以考慮 Java 提供的 Cleaner 機制或者其他替代辦法易核。
知識擴展
- final 不是 immutable 匈织!
當 final 修飾對象時,實際上只能約束指向這個對象的引用不可以被重新賦值牡直,但是對象內部的行為不被 final 影響缀匕,典型的行為就是 final 修飾普通 List 時,普通 List 仍然可以添加碰逸、刪除對象乡小。另外,如果要使 List 實現(xiàn) immutable饵史,可以考慮 List.of() 方法满钟。
- 如何構建一個 Immutable 對象
Immutable 在很多場景是非常棒的選擇掸哑,某種意義上說,Java 語言目前并沒有原生的不可變支持零远,如果需要實現(xiàn) Immutable 的類苗分,我們需要做到:
- 將 class 自身聲明為 final,這樣別人就不能通過擴展來繞過限制了牵辣。
- 將所有成員變量定義為 private 和 final摔癣,并且不要實現(xiàn) setter 方法。
- 通常構造對象時纬向,成員變量使用深度拷貝來初始化择浊,而不是直接賦值,這是一種防御措施逾条,因為你無法確定輸入對象不被其他人修改琢岩。
- 如果確實需要實現(xiàn) gettter 方法,或者其他可能會返回內部對象的方法师脂,使用 copy-on-write 原則担孔,創(chuàng)建私有的 copy。
- finalize 真的那么不堪嗎吃警?
答案是肯定的糕篇。
- finalize 會拖慢垃圾收集,對于消耗高頻的資源酌心,這是不可忍受的拌消。
finalize 被設計成在垃圾收集之前調用,一旦實現(xiàn)了非空的 finalize 方法安券,就會導致回收呈現(xiàn)數(shù)量級上的變慢墩崩,有人專門做過 benchmark,大概是 40~50 倍的下降侯勉。
實踐中鹦筹,因為 finalize 拖慢垃圾收集,導致大量對象堆積壳鹤,這也是一種典型的 OOM 的原因盛龄。
- finalize 回掩蓋資源回收時出錯的信息饰迹,下面這段代碼節(jié)選自 JDK 中的 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();
}
- 替代 finalize 的方法芳誓。
Java 平臺目前正在逐步使用 java.lang.ref.Cleaner 來替換掉原有的 finalize 實現(xiàn)。 Cleaner 的實現(xiàn)利用了幻象引用(PhantomReference)啊鸭,這是一種常見的所謂 post-mortem 清理機制锹淌。
下面是一段 JDK 提供的 Cleaner 的樣例
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();
}
}
吸取了 finalize 的教訓,每個 Cleaner 的操作都是獨立的赠制,它有自己的運行線程赂摆,可以避免意外死鎖等問題挟憔。
但是從可預測的角度來判斷,Cleaner 或者幻象引用改善的程度仍然是有限的烟号,由于種種原因導致幻象引用堆積绊谭,同樣回出現(xiàn)問題。所以汪拥,Cleaner 適合作為一種最后的保證手段达传,而不是完全依賴 Cleaner 進行資源回收。