1. 程式人生 > >Java 中的 final、finally、finalize 有什麼不同?

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 是不推薦使用的,究竟為什麼不推薦使用呢?

  1. finalize 的執行是和垃圾收集關聯在一起的,一旦實現了非空的 finalize 方法,就會導致相應物件回收呈現數量級上的變慢。
  2. finalize 被設計成在物件被垃圾收集前呼叫,JVM 要對它進行額外處理。finalize 本質上成為了快速回收的阻礙者,可能導致物件經過多個垃圾收集週期才能被回收。
  3. finalize 拖慢垃圾收集,導致大量物件堆積,也是一種典型的導致 OOM 的原因。
  4. 要確保回收資源就是因為資源都是有限的,垃圾收集時間的不可預測,可能會極大加劇資源佔用。
  5. 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-- finallythrowthrows 的用法

一、try {..} catch {..}finally {..}用法 try {   執行的程式碼,其中可能有異常。一旦發現異常,則立即跳到catch執行。否則不會執行catch裡面的內容 } catch (Exception e) {  除非try裡面執行程式碼發生了異常,否則這裡的程式碼不會執行 }

Java語言final&finally&finalize區別

                                          &

Javafinalfinallyfinalize什麽區別?

ava 部分 控制 重寫 最終 垃圾回收 final 釋放資源 特殊情況 final、finally和finalize的區別是什麽? final: 最終的意思,可以修飾類,方法和變量。 它修飾的類,不能被繼承 它修飾的方法,不能被重寫 它修飾的變量,不能被改變 fin

Java finalfinallyfinalize 什麼不同

Java 中 final、finally、finalize 有什麼不同?這是在 Java 面試中經常問到的問題,他們究竟有什麼不同呢? 這三個看起來很相似,其實他們的關係就像卡巴斯基和巴基斯坦一樣有基巴關係。 那麼如果被問到這個問題該怎麼回答呢?首先可以從語法和使用角度出發簡單介紹三者的不同: final

Javafinalfinallyfinalize的區別和用法

1.簡單區別 final用於宣告屬性,方法和類,分別表示屬性不可交變,方法不可覆蓋,類不可繼承。 finally是異常處理語句結構的一部分,表示總是執行。 finalize是Object類的一個方法,在垃圾收集器執行的時候會呼叫被回收物件的此方法,供垃圾收集時的其他資源回收,例

Javafinalfinallyfinalize的區別

1.2 定義方法當final用來定義一個方法時,會有什麼效果呢?正如大家所知,它表示這個方法不可以被子類重寫,但是它這不影響它被子類繼承。我們寫段程式碼來驗證一下: Java程式碼public class ParentClass {     public final void TestFinal() { 

Javafinalfinallyfinalize的區別

final、finally、finalize的區別 1、final修飾符(關鍵字)。被final修飾的類,就意味著不能再派生出新的子類,不能作為父類而被子類繼承。因此一個類不能既被abstract宣告

Javafinalfinallyfinalize 的區別

1.final:Java中的修飾符、關鍵字 final是java中的修飾符,用於修飾屬性(變數)、方法、類。代表屬性值不可修改、方法不可覆蓋、類不可繼承。 當變數被宣告final時,必須要給定初值,而在以後的引用中只能讀取,不能修改。 例項: 上圖我們定義了final 字

Java核心-03 談談finalfinally finalize什麽不同

推薦 垃圾 源碼 私有 pri jdk 收集 hotspot 減少 今天,我要問你的是一個經典的 Java 基礎題目,談談 final、finally、 finalize 有什麽不同? 典型回答 final 可以用來修飾類、方法、變量,分別有不同地意義,final修飾

finalfinallyfinalize什麼不同

        final可以用來修飾類、方法、變數,分別有不同的意義,final修飾的類代表不可以繼承擴充套件,final的變數是不可以修改的,而final的方法也是不可以重寫的。         finally則

第三講 談談finalfinallyfinalize什麼不同

final 可以用來修飾類、方法、變數,分別有不同的意義,nal 修飾的 class 代表不可以繼承擴充套件,final 的變數是不可以修改的,而 final 的方法也是不可以重寫的(override)。 finally 則是 Java 保證重點程式碼一定要被執

java final finallyfinalize的區別

final:在java中,final一般是指不可變的,是一個修飾符,可以修飾常量、方法、類, public class TestOne{ final int MAX_ONE=1; public void test(){ MAX_ONE=2;//在這裡是錯誤的,變數被final修飾後不能再

finalfinally finalize什麼不同

一、final 不可變 通常用來修飾一個類或者一個方法或者一個變數 1.修飾類的時候,表示這個類是不可以被繼承的。(避免重寫方法,更改一些功能) 2.修飾方法的時候,表示這個方法是不可以被重寫的。(同上) 3.修飾變數的時候,表示這個變數的引用是不可以更改的。(保證變

finalfinally finalize什麼不同

final 中文翻譯:最終的;決定性的;不可更改的,可以用來修飾類、方法、變數,分別有不同的意義 修飾類:當用final修飾一個類時,表明這個類不能被繼承。也就是說,這個類不能其他類繼承(反向說如果不想讓這個類被繼承就使用final進行修飾類)。final類中的成員變數可以根

javafinalfinallyfinalize的區別

final :java 關鍵字。被final修飾的變數不可進行值更改,必須在定義時一併初始化。如final int i=1,則下面對i只能使用,而不能進行更改如i++,更改必定會報錯。同理,final修飾方法時,則子類不能對該方法進行重寫;被final修飾的類不允許繼承。所以

finalfinallyfinalize分別什麼作用

1、final可以修飾變數、方法、類 final修飾變量表示這個變數就是個常量;(final修飾基本資料型別時,表示這個變數是一個常量,final修飾一個物件時表示這個物件的引用是不可修改的,但物件的

Java基礎總結從0開始(二):finalfinallyfinalize的區別

final:用於修飾類、方法和屬性;被修飾的類不能被繼承,方法不能重寫,屬性不可改變即參量;        -----ps:abstract和final不能同時修飾類finally:多用捕獲異常後必須執行執行的程式碼塊,比例關閉連線,IO流等;finaliza:JVM在回收垃

java面試之Finalfinallyfinalize區別

1、final Final可以用於成員變數(包括方法引數),方法、類。 Final成員  作為變數 變數一旦被初始化便不可改變(對於基本型別,指的是值不變;對於物件型別,指的是引用不變),初始化只可能在兩個地方:定義處和建構函式。 作為方法引數 對於基本型別,定義成fi

Java核心技術36講 第三講:finalfinallyfinalize不同

final、finally、finalize final可以用來修飾類、方法、變數,分別有不同的意義, final修飾的class不可以繼承擴充套件,final的變數不可以被修改,final的方法不

JAVA面試題解惑系列 – finalfinallyfinalize的區別

這是一道再經典不過的面試題了,我們在各個公司的面試題中幾乎都能看到它的身影。 final、finally和finalize雖然長得像孿生三兄弟一樣,但是它們的含義和用法卻是大相徑庭。 這一次我們就一起來回顧一下這方面的知識。 final關鍵字 我們首先來說說final。它可以用於以下四個地方: 1. 定義變