沒有一名程序員希望自己在寫程序的時(shí)候遇到異常,但是實(shí)際上異常是無法避免的,沒有人能保證寫出的程序不會(huì)出錯(cuò),已無法保證用戶會(huì)按照程序員的意愿來使用程序枝冀。既然異常無法避免,對異常的處理也就是必需的耘子。異常處理已成為衡量一門語言是否成熟的標(biāo)準(zhǔn)之一果漾,增加了異常處理機(jī)制后的程序有更好的容錯(cuò)性、更加健壯谷誓。目前的主流編程語言(如C++绒障、C#、Python等)都提供了這種機(jī)制捍歪,Java也不例外户辱。Java的異常機(jī)制主要依賴于try
、catch
糙臼、finally
庐镐、throws
和throw
這五個(gè)關(guān)鍵字。
最基本的異常處理——try...catch
下面是Java異常處理機(jī)制的語法結(jié)構(gòu):
try{
//業(yè)務(wù)邏輯代碼
...
}catch(Exception e){
//異常處理代碼
...
}
如果try
塊代碼出現(xiàn)異常变逃,系統(tǒng)會(huì)自動(dòng)生成一個(gè)異常對象必逆,該異常對象被提交給Java運(yùn)行時(shí)環(huán)境(Runtime Environment),這個(gè)過程稱為拋出(throw)異常。
當(dāng)Java運(yùn)行時(shí)環(huán)境接收到異常對象時(shí)名眉,會(huì)自動(dòng)尋找處理該異常對象的catch
塊粟矿。如果找到了合適的catch
塊,則把該異常對象交給改catch
塊處理璧针,這個(gè)過程稱為捕獲(catch)異常;如果Java運(yùn)行時(shí)環(huán)境無法找到捕獲異常的catch
塊渊啰,則運(yùn)行時(shí)環(huán)境終止探橱,Java程序也隨之退出。這就是Java中最基本的異常處理機(jī)制绘证,即先拋出異常隧膏,再捕獲異常(并處理)。
注意:try
塊與if
語句不一樣嚷那,即使try
塊里只有一條語句胞枕,花括號(hào){}
也不能省略。
異常類的繼承體系
catch
塊都是專門用于處理異常類及其子類的異常實(shí)例魏宽。這句戶乍一看很難理解腐泻,來看下面的例子:
結(jié)合上圖進(jìn)行解釋:當(dāng)Java運(yùn)行時(shí)環(huán)境接收到異常對象后,會(huì)一次判斷該異常對象是否是catch
塊后異常類或其子類的實(shí)例队询,如果是派桩,Java運(yùn)行時(shí)環(huán)境將調(diào)用該catch
塊來處理該異常;否則將再次拿該異常對象和下一個(gè)catch
塊后的異常類進(jìn)行比較蚌斩,直至滿足條件铆惑。
因此,try
塊后可以有多個(gè)catch
塊送膳,這是為了針對不同的異常類提供不同的異常處理方式员魏。當(dāng)系統(tǒng)發(fā)生不同的意外情況時(shí),系統(tǒng)會(huì)生成不同的異常對象叠聋,Java運(yùn)行時(shí)環(huán)境就會(huì)根據(jù)該異常對象所屬的異常類來決定使用哪個(gè)catch
塊來處理該異常撕阎。同時(shí),通過在try
塊后提供多個(gè)catch
塊碌补,也無須在一個(gè)catch
塊內(nèi)使用if
或switch
等語句判斷異常類型進(jìn)而采取不同的處理方式闻书,使得異常處理邏輯更加細(xì)致、更有條理脑慧。
再來看一張圖:
Java把所有的非正常情況分成兩種:異常(Exception)和錯(cuò)誤(Error)魄眉。Error錯(cuò)誤,一般是指與虛擬機(jī)(JVM)相關(guān)的問題闷袒,如系統(tǒng)崩潰坑律、虛擬機(jī)錯(cuò)誤、動(dòng)態(tài)連接失敗等,這種錯(cuò)誤無法恢復(fù)或不可能捕獲晃择,將導(dǎo)致應(yīng)用程序中斷冀值。通常應(yīng)用程序無法處理這些錯(cuò)誤,因此應(yīng)用程序不應(yīng)該試圖使用catch
塊來捕獲Error對象宫屠。而對于異常列疗,看個(gè)例子:
public class NullTest {
public static void main(String[] args) {
Date d = null;
try {
System.out.println(d.after(new Date()));
} catch (NullPointerException e) {
System.out.println("空指針異常!");
} catch (Exception e) {
System.out.println("未知異常浪蹂!");
}
}
}
上面程序調(diào)用了一個(gè)null
對象的after()
方法抵栈,因此引發(fā)了NullPointerException
。注意到程序中把對應(yīng)Exception
類的catch
塊放在最后坤次,根據(jù)異常類的繼承體系可知古劲,這是因?yàn)槿绻褜?yīng)Exception
類的catch
塊放在其他catch
塊的前面,出現(xiàn)異常后程序會(huì)直接進(jìn)入該catch
塊缰猴,而排在它后面的catch
塊將永遠(yuǎn)不會(huì)被執(zhí)行产艾,這就違背了上文提到的對不同異常進(jìn)行不同處理的原則。
實(shí)際上滑绒,進(jìn)行異常捕獲時(shí)不僅應(yīng)該把Exception
類對應(yīng)的catch
塊放在最后闷堡,而且所有父類異常的catch
塊都應(yīng)該排在子類異常catch
塊的后面(簡稱:先處理小異常,再處理大異常)疑故,否則會(huì)出現(xiàn)編譯錯(cuò)誤缚窿。將上面的代碼稍作修改:
...
try {
System.out.println(d.after(new Date()));
} catch (Exception e) {
System.out.println("未知異常!");
} catch (NullPointerException e) {
System.out.println("空指針異常焰扳!");
}
這時(shí)程序報(bào)錯(cuò):
錯(cuò)誤信息是:這個(gè)異常已被Exception
類的catch
塊捕獲了倦零。
訪問異常信息
所有的異常對象都包含下面四個(gè)方法:
-
getMessage()
:返回該異常的詳細(xì)描述字符串; -
printStackTrace()
:將該異常的跟蹤棧信息輸出到標(biāo)準(zhǔn)錯(cuò)誤輸出吨悍; -
printStackTrace(PrintStream s)
:將該異常的跟蹤棧信息輸出到指定輸出流扫茅; -
getStackTrace()
:返回該異常的跟蹤棧信息。
還是剛才的例子:
...
try {
System.out.println(d.after(new Date()));
} catch (NullPointerException e) {
e.printStackTrace();
}
控制臺(tái)輸出為:
使用finally回收資源
有些時(shí)候育瓜,程序在try
塊里打開了一些物理資源(例如數(shù)據(jù)庫連接葫隙、網(wǎng)絡(luò)連接和磁盤文件等),這些物理資源都必須顯示回收躏仇,因?yàn)镴ava的垃圾回收機(jī)制只回收堆內(nèi)存中對象所占用的內(nèi)存恋脚,不會(huì)回收任何物理資源。為解決這個(gè)問題焰手,Java的異常處理機(jī)制提供了finally
塊糟描。不管try
塊中的代碼是否出現(xiàn)異常,也不管哪一個(gè)catch
塊被執(zhí)行书妻,甚者在try
塊或者catch
塊中執(zhí)行了return
語句船响,finally
塊總會(huì)被執(zhí)行。因此,完整的Java異常處理語法結(jié)構(gòu)為:
try{
//業(yè)務(wù)邏輯代碼
...
}catch(Exception e){
//異常處理代碼
...
}...
finally{
//資源回收代碼
}
注意:異常語法結(jié)構(gòu)中只有try
塊是必需的见间;catch
塊和finally
塊都是可選的聊闯,但至少出現(xiàn)其中之一,也可以同時(shí)出現(xiàn)米诉;finally
塊必須位于所有的catch
塊之后菱蔬。
來看一個(gè)例子:
try {
System.out.println(d.after(new Date()));
} catch (NullPointerException e) {
e.printStackTrace();
return;
} finally {
System.out.println("finally塊里的語句被執(zhí)行了!");
}
控制臺(tái)輸出為:
程序的catch
塊有一條return
語句史侣。在通常情況下拴泌,一旦在方法里執(zhí)行到return
語句的地方,程序?qū)⒘⒓唇Y(jié)束該方法抵窒。但由控制臺(tái)輸出的結(jié)果可知弛针,雖然return
語句也強(qiáng)制方法結(jié)束叠骑,但一定會(huì)執(zhí)行finally
塊里的語句李皇。
作為對比,對上面的代碼稍作修改:
try {
System.out.println(d.after(new Date()));
} catch (NullPointerException e) {
e.printStackTrace();
System.exit(1);
} finally {
System.out.println("finally塊里的語句被執(zhí)行了宙枷!");
}
再看控制臺(tái)的輸出:
程序中使用System.exit(1)
語句來退出虛擬機(jī)掉房,此時(shí)finally
塊將失去被執(zhí)行的機(jī)會(huì)。