final、finally和finalize的差別是什麼?
這是一道再經典不過的面試題了,我們在各個公司的面試題中幾乎都能看到它的身影。final、finally和finalize雖然長得像孿生三兄弟一樣,但是它們的含義和用法卻是大相徑庭。這一次我們就一起來回顧一下這方面的知識。
final關鍵字
我們首先來說說final。它可以用于以下四個地方:
定義變量,包括靜态的和非靜态的。
定義方法的參數。
定義方法。
定義類。
我們依次來回顧一下每種情況下final的作用。首先來看第一種情況,如果final修飾的是一個基本類型,就表示這個變量被賦予的值是不可變的,即它是個常量;如果final修飾的是一個對象,就表示這個變量被賦予的引用是不可變的,這裡需要提醒大家注意的是,不可改變的隻是這個變量所儲存的引用,并不是這個引用所指向的對象。在第二種情況下,final的含義與第一種情況相同。實際上對于前兩種情況,有一種更貼切的表述final的含義的描述,那就是,如果一個變量或方法參數被final修飾,就表示它隻能被指派一次,但是java虛拟機為變量設定的預設值不記作一次指派。
被final修飾的變量必須被初始化。初始化的方式有以下幾種:
在定義的時候初始化。
final變量可以在初始化塊中初始化,不可以在靜态初始化塊中初始化。
靜态final變量可以在靜态初始化塊中初始化,不可以在初始化塊中初始化。
final變量還可以在類的構造器中初始化,但是靜态final變量不可以。
通過下面的代碼可以驗證以上的觀點:
java代碼
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZukHcvN2Xu92Yp9CXzhGdhB3YpB3X39FcvwVbvNmLllXZhZXYq5iblJXald3ZuFmevw1LcpDc0RHaiojIsJye.gif)
public class finaltest {
// 在定義時初始化
public final int a = 10;
public final int b;
// 在初始化塊中初始化
{
b = 20;
}
// 非靜态final變量不能在靜态初始化塊中初始化
// public final int c;
// static {
// c = 30;
// }
// 靜态常量,在定義時初始化
public static final int static_d = 40;
public static final int static_e;
// 靜态常量,在靜态初始化塊中初始化
static {
static_e = 50;
// 靜态變量不能在初始化塊中初始化
// public static final int static_f;
// {
// static_f = 60;
public final int g;
// 靜态final變量不可以在構造器中初始化
// public static final int static_h;
// 在構造器中初始化
public finaltest() {
g = 70;
// 靜态final變量不可以在構造器中初始化
// static_h = 80;
// 給final的變量第二次指派時,編譯會報錯
// a = 99;
// static_d = 99;
// final變量未被初始化,編譯時就會報錯
// public final int i;
// 靜态final變量未被初始化,編譯時就會報錯
// public static final int static_j;
}
我們運作上面的代碼之後出了可以發現final變量(常量)和靜态final變量(靜态常量)未被初始化時,編譯會報錯。
用final修飾的變量(常量)比非final的變量(普通變量)擁有更高的效率,是以我們在實際程式設計中應該盡可能多的用常量來代替普通變量,這也是一個很好的程式設計習慣。
當final用來定義一個方法時,會有什麼效果呢?正如大家所知,它表示這個方法不可以被子類重寫,但是它這不影響它被子類繼承。我們寫段代碼來驗證一下:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZukHcvN2Xu92Yp9CXzhGdhB3YpB3X39FcvwVbvNmLllXZhZXYq5iblJXald3ZuFmevw1LcpDc0RHaiojIsJye.gif)
class parentclass {
public final void testfinal() {
system.out.println("父類--這是一個final方法");
}
public class subclass extends parentclass {
/**
* 子類無法重寫(override)父類的final方法,否則編譯時會報錯
*/
// public void testfinal() {
// system.out.println("子類--重寫final方法");
public static void main(string[] args) {
subclass sc = new subclass();
sc.testfinal();
這裡需要特殊說明的是,具有private通路權限的方法也可以增加final修飾,但是由于子類無法繼承private方法,是以也無法重寫它。編譯器在處理private方法時,是按照final方法來對待的,這樣可以提高該方法被調用時的效率。不過子類仍然可以定義同父類中的private方法具有同樣結構的方法,但是這并不會産生重寫的效果,而且它們之間也不存在必然聯系。
最後我們再來回顧一下final用于類的情況。這個大家應該也很熟悉了,因為我們最常用的string類就是final的。由于final類不允許被繼承,編譯器在處理時把它的所有方法都當作final的,是以final類比普通類擁有更高的效率。而由關鍵字abstract定義的抽象類含有必須由繼承自它的子類重載實作的抽象方法,是以無法同時用final和abstract來修飾同一個類。同樣的道理,final也不能用來修飾接口。final的類的所有方法都不能被重寫,但這并不表示final的類的屬性(變量)值也是不可改變的,要想做到final類的屬性值不可改變,必須給它增加final修飾,請看下面的例子:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZukHcvN2Xu92Yp9CXzhGdhB3YpB3X39FcvwVbvNmLllXZhZXYq5iblJXald3ZuFmevw1LcpDc0RHaiojIsJye.gif)
public final class finaltest {
int i = 10;
finaltest ft = new finaltest();
ft.i = 99;
system.out.println(ft.i);
運作上面的代碼試試看,結果是99,而不是初始化時的10。
finally語句
接下來我們一起回顧一下finally的用法。這個就比較簡單了,它隻能用在try/catch語句中,并且附帶着一個語句塊,表示這段語句最終總是被執行。請看下面的代碼:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZukHcvN2Xu92Yp9CXzhGdhB3YpB3X39FcvwVbvNmLllXZhZXYq5iblJXald3ZuFmevw1LcpDc0RHaiojIsJye.gif)
public final class finallytest {
try {
throw new nullpointerexception();
} catch (nullpointerexception e) {
system.out.println("程式抛出了異常");
} finally {
system.out.println("執行了finally語句塊");
}
運作結果說明了finally的作用:
程式抛出了異常
執行了finally語句塊
請大家注意,捕獲程式抛出的異常之後,既不加處理,也不繼續向上抛出異常,并不是良好的程式設計習慣,它掩蓋了程式執行中發生的錯誤,這裡隻是友善示範,請不要學習。
那麼,有沒有一種情況使finally語句塊得不到執行呢?大家可能想到了return、continue、break這三個可以打亂代碼順序執行語句的規律。那我們就來試試看,這三個語句是否能影響finally語句塊的執行:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZukHcvN2Xu92Yp9CXzhGdhB3YpB3X39FcvwVbvNmLllXZhZXYq5iblJXald3ZuFmevw1LcpDc0RHaiojIsJye.gif)
// 測試return語句
public returnclass testreturn() {
return new returnclass();
} catch (exception e) {
e.printstacktrace();
system.out.println("執行了finally語句");
return null;
// 測試continue語句
public void testcontinue() {
for (int i = 0; i < 3; i++) {
try {
system.out.println(i);
if (i == 1) {
continue;
}
} catch (exception e) {
e.printstacktrace();
} finally {
system.out.println("執行了finally語句");
}
// 測試break語句
public void testbreak() {
break;
finallytest ft = new finallytest();
// 測試return語句
ft.testreturn();
system.out.println();
// 測試continue語句
ft.testcontinue();
// 測試break語句
ft.testbreak();
class returnclass {
public returnclass() {
system.out.println("執行了return語句");
上面這段代碼的運作結果如下:
執行了return語句
執行了finally語句
1
2
很明顯,return、continue和break都沒能阻止finally語句塊的執行。從輸出的結果來看,return語句似乎在finally語句塊之前執行了,事實真的如此嗎?我們來想想看,return語句的作用是什麼呢?是退出目前的方法,并将值或對象傳回。如果finally語句塊是在return語句之後執行的,那麼return語句被執行後就已經退出目前方法了,finally語句塊又如何能被執行呢?是以,正确的執行順序應該是這樣的:編譯器在編譯return new returnclass();時,将它分成了兩個步驟,new returnclass()和return,前一個建立對象的語句是在finally語句塊之前被執行的,而後一個return語句是在finally語句塊之後執行的,也就是說finally語句塊是在程式退出方法之前被執行的。同樣,finally語句塊是在循環被跳過(continue)和中斷(break)之前被執行的。
finalize方法
最後,我們再來看看finalize,它是一個方法,屬于java.lang.object類,它的定義如下:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZukHcvN2Xu92Yp9CXzhGdhB3YpB3X39FcvwVbvNmLllXZhZXYq5iblJXald3ZuFmevw1LcpDc0RHaiojIsJye.gif)
protected void finalize() throws throwable { }
衆所周知,finalize()方法是gc(garbage collector)運作機制的一部分,關于gc的知識我們将在後續的章節中來回顧。
在此我們隻說說finalize()方法的作用是什麼呢?
finalize()方法是在gc清理它所從屬的對象時被調用的,如果執行它的過程中抛出了無法捕獲的異常(uncaught exception),gc将終止對改對象的清理,并且該異常會被忽略;直到下一次gc開始清理這個對象時,它的finalize()會被再次調用。
請看下面的示例:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZukHcvN2Xu92Yp9CXzhGdhB3YpB3X39FcvwVbvNmLllXZhZXYq5iblJXald3ZuFmevw1LcpDc0RHaiojIsJye.gif)
// 重寫finalize()方法
protected void finalize() throws throwable {
system.out.println("執行了finalize()方法");
ft = null;
system.gc();
運作結果如下:
執行了finalize()方法
程式調用了java.lang.system類的gc()方法,引起gc的執行,gc在清理ft對象時調用了它的finalize()方法,是以才有了上面的輸出結果。調用system.gc()等同于調用下面這行代碼:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZukHcvN2Xu92Yp9CXzhGdhB3YpB3X39FcvwVbvNmLllXZhZXYq5iblJXald3ZuFmevw1LcpDc0RHaiojIsJye.gif)
runtime.getruntime().gc();
調用它們的作用隻是建議垃圾收集器(gc)啟動,清理無用的對象釋放記憶體空間,但是gc的啟動并不是一定的,這由java虛拟機來決定。直到java虛拟機停止運作,有些對象的finalize()可能都沒有被運作過,那麼怎樣保證所有對象的這個方法在java虛拟機停止運作之前一定被調用呢?答案是我們可以調用system類的另一個方法:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZukHcvN2Xu92Yp9CXzhGdhB3YpB3X39FcvwVbvNmLllXZhZXYq5iblJXald3ZuFmevw1LcpDc0RHaiojIsJye.gif)
public static void runfinalizersonexit(boolean value) {
//other code
給這個方法傳入true就可以保證對象的finalize()方法在java虛拟機停止運作前一定被運作了,不過遺憾的是這個方法是不安全的,它會導緻有用的對象finalize()被誤調用,是以已經不被贊成使用了。
由于finalize()屬于object類,是以所有類都有這個方法,object的任意子類都可以重寫(override)該方法,在其中釋放系統資源或者做其它的清理工作,如關閉輸入輸出流。
通過以上知識的回顧,我想大家對于final、finally、finalize的用法差別已經很清楚了。