【Java面試題系列】:Java中final finally finalize的區別

按個人我的理解,這個題目自己就問的有點問題,由於這3個關鍵字之間沒啥關係,是相對獨立的,我猜測這道題的初衷應該是想了解面試者對Java中final finally finalize的使用方法的掌握狀況,只是由於3個關鍵字比較像,而成了如今網上流傳的題目「Java中final finally finalize的區別」。java

既然是想了解面試者對Java中final finally finalize的使用方法的掌握狀況,那麼本篇咱們就分別講解下final,finally,finalize的使用方法。面試

1.final用法

咱們先看下final的英文釋義:最終的;決定性的;不可更改的,不由要推測被final修飾的變量,方法或者類是否是不可修改的呢?spring

1.1final修飾類

在Java中,被final修飾的類,不能被繼承,也就是final類的成員方法沒有機會被繼承,也沒有機會被重寫。springboot

在設計類的時候,若是這個類不須要有子類,類的實現細節不容許改變,那麼就能夠設計爲final類。bash

如咱們在開發中常用的String類就是final類,如下爲部分源碼:ide

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    ......
}    複製代碼

1.2final修飾方法

在Java中,被final修飾的方法,能夠被繼承,但不能被子類重寫(覆蓋)。函數

在設計方法時,若是這個方法不但願被子類重寫(覆蓋),那麼就能夠設計爲final方法。ui

舉個具體的例子,咱們新建個父類Animal以下:this

package com.zwwhnly.springbootdemo;

public class Animal {
    public void eat() {
        System.out.println("Animal eat.");
    }

    public void call() {
        System.out.println("Animal call.");
    }

    public final void fly() {
        System.out.println("Animal fly.");
    }

    private final void swim() {
        System.out.println("Animal swim.");
    }
}複製代碼

而後定義一個子類Cat繼承Animal類,代碼以下:spa

package com.zwwhnly.springbootdemo;

public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("Cat eat.");
    }

    @Override
    public void fly() {
        System.out.println("Cat fly.");
    }

    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.eat();
        cat.call();
        cat.fly();
        cat.swim();
    }
}複製代碼

咱們會發現,以上代碼中有2個錯誤

1)當咱們重寫fly()方法時,由於父類的fly()方法被定義爲final方法,重寫時會編譯錯誤

2)cat.swim();報錯,由於父類的swim()方法被定義爲private,子類是繼承不到的

而後咱們將報錯的代碼刪除,運行結果以下:

Cat eat.

Animal call.

Animal fly.

也就是eat()方法被子類重寫了,繼承了父類的成員方法call()和final方法fly()。

可是值得注意的是,在子類Cat中,咱們是能夠從新定義父類的私有final方法swim()的,不過此時明顯不是重寫(你能夠加@Override試試,會編譯報錯),而是子類本身的成員方法swim()。

package com.zwwhnly.springbootdemo;

public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("Cat eat.");
    }

    public void swim() {
        System.out.println("Cat swim.");
    }

    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.eat();
        cat.call();
        cat.fly();
        cat.swim();
    }
}複製代碼

此時的運行結果爲:

Cat eat.

Animal call.

Animal fly.

Cat swim.

1.3final修飾成員變量

用final修飾的成員變量沒有默認值,能夠在聲明時賦值或者在構造函數中賦值,但必須賦值且只能被賦值1次,賦值後沒法修改。

咱們修改下1.2中的Cat類代碼,定義2個final成員變量,1個聲明完當即賦值,1個在構造函數中賦值:

package com.zwwhnly.springbootdemo;

public class Cat extends Animal {
    private final int age = 1;
    private final String name;

    public Cat(String name) {
        this.name = name;
    }

    @Override
    public void eat() {
        System.out.println("Cat eat.");
    }

    public static void main(String[] args) {
        Cat whiteCat = new Cat("小白");
        whiteCat.age = 2;
        System.out.println(whiteCat.age);
        System.out.println(whiteCat.name);

        Cat blackCat = new Cat("小黑");
        blackCat.name = "小黑貓";
        System.out.println(blackCat.age);
        System.out.println(blackCat.name);
    }
}複製代碼

以上代碼有2個編譯錯誤,1個是whiteCat.age = 2;修改爲員變量age時,另1個是blackCat.name = "小黑貓";修改爲員變量name時,都提示不能修改final成員變量。

刪除掉錯誤的代碼,運行結果以下:

1

小白

1

小黑

1.4final修飾局部變量

被final修飾的局部變量,既能夠在聲明時當即賦值,也能夠先聲明,後賦值,但只能賦值一次,不能夠重複賦值。

修改下Cat類的eat()方法以下:

@Override
public void eat() {

    final String breakfast;
    final String lunch = "午飯";
    breakfast = "早餐";
    lunch = "午飯2";
    breakfast = "早餐2";

    System.out.println("Cat eat.");
}複製代碼

以上代碼中2個錯誤,1個是lunch = "午飯2";,1個是breakfast = "早餐2";,都是對final局部變量第2次賦值時報錯。

1.5final修飾方法參數

方法參數其實也是局部變量,所以final修飾方法參數和1.4中final修飾局部變量的使用相似,即方法中只能使用方法的參數值,但不能修改參數值。

在Cat類中新增方法printCatName,將方法參數修飾爲final:

public static void main(String[] args) {
    Cat whiteCat = new Cat("小白");
    whiteCat.printCatName(whiteCat.name);
}

public void printCatName(final String catName) {
    //catName = "修改catName";    // 該行語句會報錯
    System.out.println(catName);
}複製代碼

運行結果:

小白

2.finally用法

提起finally,你們都知道,這是Java中處理異常的,一般和try,catch一塊兒使用,主要做用是無論代碼發不發生異常,都會保證finally中的語句塊被執行。

你是這樣認爲的嗎?說實話,哈哈。

那麼問題來了,finally語句塊必定會被執行嗎?,答案是不必定

讓咱們經過具體的示例來證實該結論。

2.1在 try 語句塊以前返回(return)或者拋出異常,finally不會被執行

package com.zwwhnly.springbootdemo;

public class FinallyTest {
    public static void main(String[] args) {
        System.out.println("return value of test():" + test());
    }

    public static int test() {
        int i = 1;
        /*if (i == 1) {
            return 0;
        }*/
        System.out.println("the previous statement of try block");
        i = i / 0;
        try {
            System.out.println("try block");
            return i;
        } finally {
            System.out.println("finally block");
        }
    }
}複製代碼

運行結果以下:

也就是說,以上示例中,finally語句塊沒有被執行。

而後咱們將上例中註釋的代碼取消註釋,此時運行結果爲:

return value of test():0

finally語句塊仍是沒有被執行,所以,咱們能夠得出結論:

只有與 finally 相對應的 try 語句塊獲得執行的狀況下,finally 語句塊纔會執行。

以上兩種狀況,都是在 try 語句塊以前返回(return)或者拋出異常,因此 try 對應的 finally 語句塊沒有執行。

2.2與 finally 相對應的 try 語句塊獲得執行,finally不必定會被執行

那麼,與 finally 相對應的 try 語句塊獲得執行的狀況下,finally 語句塊必定會執行嗎?答案仍然是不必定。

看下下面這個例子:

package com.zwwhnly.springbootdemo;

public class FinallyTest {
    public static void main(String[] args) {
        System.out.println("return value of test():" + test());
    }

    public static int test() {
        int i = 1;
        try {
            System.out.println("try block");
            System.exit(0);
            return i;
        } finally {
            System.out.println("finally block");
        }
    }
}複製代碼

運行結果爲:

try block

finally語句塊仍是沒有被執行,爲何呢?由於咱們在try語句塊中執行了System.exit(0);,終止了Java虛擬機的運行。固然,通常狀況下,咱們的應用程序中是不會調用System.exit(0);的,那麼,若是不調用這個方法,finally語句塊必定會被執行嗎?

答案固然仍是不必定,當一個線程在執行 try 語句塊或者 catch 語句塊時被打斷(interrupted)或者被終止(killed),與其相對應的 finally 語句塊可能不會執行。還有更極端的狀況,就是在線程運行 try 語句塊或者 catch 語句塊時,忽然死機或者斷電,finally 語句塊確定不會執行了。固然,死機或者斷電屬於極端狀況,在這裏只是爲了證實,finally語句塊不必定會被執行。

2.3try語句塊或者catch語句塊中有return語句

若是try語句塊中有return語句, 是return語句先執行仍是finally語句塊先執行呢?

帶着這個問題,咱們看下以下這個例子:

package com.zwwhnly.springbootdemo;

public class FinallyTest {
    public static void main(String[] args) {
        try {
            System.out.println("try block");
            return;
        } finally {
            System.out.println("finally block");
        }
    }
}複製代碼

運行結果:

try block

finally block

結論:finally 語句塊在 try 語句塊中的 return 語句以前執行。

若是catch語句塊中有return語句,是return語句先執行仍是finally語句塊先執行呢?

帶着這個問題,咱們看下以下這個例子:

package com.zwwhnly.springbootdemo;

public class FinallyTest {
    public static void main(String[] args) {
        System.out.println("return value of test():" + test());
    }

    public static int test() {
        int i = 1;
        try {
            System.out.println("try block");
            i = i / 0;
            return 1;
        } catch (Exception e) {
            System.out.println("catch block");
            return 2;
        } finally {
            System.out.println("finally block");
        }
    }
}複製代碼

運行結果:

try block

catch block

finally block

return value of test():2

結論:finally 語句塊在 catch 語句塊中的 return 語句以前執行。

經過上面2個例子,咱們能夠看出,其實 finally 語句塊是在 try 或者 catch 中的 return 語句以前執行的。更加通常的說法是,finally 語句塊應該是在控制轉移語句以前執行,控制轉移語句除了 return 外,還有 break ,continue和throw。

2.4其它幾個例子

示例1:

package com.zwwhnly.springbootdemo;

public class FinallyTest {
    public static void main(String[] args) {
        System.out.println("return value of getValue():" + getValue());
    }

    public static int getValue() {
        try {
            return 0;
        } finally {
            return 1;
        }
    }
}複製代碼

運行結果:

return value of getValue():1

示例2:

package com.zwwhnly.springbootdemo;

public class FinallyTest {
    public static void main(String[] args) {
        System.out.println("return value of getValue():" + getValue());
    }

    public static int getValue() {
        int i = 1;
        try {
            return i;
        } finally {
            i++;
        }
    }
}複製代碼

運行結果:

return value of getValue():1

也許你會好奇,應該會返回2,怎麼返回1了呢?能夠借鑑下如下內容來理解(牽扯到了Java虛擬機如何編譯finally語句塊):

實際上,Java 虛擬機會把 finally 語句塊做爲 subroutine(對於這個 subroutine 不知該如何翻譯爲好,乾脆就不翻譯了,省得產生歧義和誤解。)直接插入到 try 語句塊或者 catch 語句塊的控制轉移語句以前。可是,還有另一個不可忽視的因素,那就是在執行 subroutine(也就是 finally 語句塊)以前,try 或者 catch 語句塊會保留其返回值到本地變量表(Local Variable Table)中。待 subroutine 執行完畢以後,再恢復保留的返回值到操做數棧中,而後經過 return 或者 throw 語句將其返回給該方法的調用者(invoker)。請注意,前文中咱們曾經提到過 return、throw 和 break、continue 的區別,對於這條規則(保留返回值),只適用於 return 和 throw 語句,不適用於 break 和 continue 語句,由於它們根本就沒有返回值。

示例3:

package com.zwwhnly.springbootdemo;

public class FinallyTest {
    public static void main(String[] args) {
        System.out.println("return value of getValue():" + getValue());
    }

    public static int getValue() {
        int i = 1;
        try {
            i = 4;
        } finally {
            i++;
            return i;
        }
    }
}複製代碼

運行結果:

return value of getValue():5

示例4:

package com.zwwhnly.springbootdemo;

public class FinallyTest {
    public static void main(String[] args) {
        System.out.println("return value of getValue():" + getValue());
    }

    public static int getValue() {
        int i = 1;
        try {
            i = 4;
        } finally {
            i++;
        }
        return i;
    }
}複製代碼

運行結果:

return value of getValue():5

示例5:

package com.zwwhnly.springbootdemo;

public class FinallyTest {
    public static void main(String[] args) {
        System.out.println(test());
    }

    public static String test() {
        try {
            System.out.println("try block");
            return test1();
        } finally {
            System.out.println("finally block");
        }
    }

    public static String test1() {
        System.out.println("return statement");
        return "after return";
    }
}複製代碼

try block

return statement

finally block

after return

2.5總結

  1. finally語句塊不必定會被執行
  2. finally 語句塊在 try 語句塊中的 return 語句以前執行。
  3. finally 語句塊在 catch 語句塊中的 return 語句以前執行。
  4. 注意控制轉移語句 return ,break ,continue,throw對執行順序的影響

3.finalize用法

finalize()是Object類的一個方法,所以全部的類都繼承了這個方法。

protected void finalize() throws Throwable { }複製代碼

finalize()主要用於在垃圾收集器將對象從內存中清除出去以前作必要的清理工做。這個方法是由垃圾收集器在肯定這個對象沒有被引用時對這個對象調用的。

子類覆蓋 finalize() 方法以整理系統資源或者執行其餘清理工做。finalize() 方法是在垃圾收集器刪除對象以前對這個對象調用的。

當垃圾回收器(GC)決定回收某對象時,就會運行該對象的finalize()方法。

不過在Java中,若是內存老是充足的,那麼垃圾回收可能永遠不會進行,也就是說filalize()可能永遠不被執行,顯然期望它作收尾工做是靠不住的。

相關文章
相關標籤/搜索