您好,登錄后才能下訂單哦!
這篇文章主要講解了“Java中的final、finally和finalize有什么不同”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Java中的final、finally和finalize有什么不同”吧!
首先可以從語法和使用角度出發簡單介紹三者的不同:
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 將方法進行內聯,可以改善編譯器進行條件編譯的能力等等。我在之前一篇文章進行了介紹,想了解的可以點擊查閱。
final 與 immutable
在前面介紹了 final 在實踐中的益處,需要注意的是,final 并不等同于 immutable,比如下面這段代碼:
final List strList = new ArrayList<>();
strList.add("wupx");
strList.add("huxy");
List 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 是被吞掉的,也就意味著一旦出現異常或者出錯,得不到任何有效信息。
有更好的方法替代 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 進行資源回收。
感謝各位的閱讀,以上就是“Java中的final、finally和finalize有什么不同”的內容了,經過本文的學習后,相信大家對Java中的final、finally和finalize有什么不同這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。