1棱诱、概述
當(dāng)方法內(nèi)部發(fā)生一項(xiàng)錯(cuò)誤時(shí)藻雪,該方法會(huì)創(chuàng)建一個(gè)對(duì)象傳遞給運(yùn)行時(shí)系統(tǒng)(runtime system),這個(gè)對(duì)象被稱為異常對(duì)象魁索,包含錯(cuò)誤的類型、發(fā)生位置盼铁,程序狀態(tài)等一系列信息粗蔚。
當(dāng)一個(gè)方法拋出異常時(shí),運(yùn)行時(shí)系統(tǒng)會(huì)沿著調(diào)用棧尋找該異常的處理方式 饶火。
下圖中鹏控,調(diào)用棧下面的方法調(diào)用了上面的方法,層層嵌套肤寝,一共四層:
調(diào)用第三個(gè)方法時(shí)拋出了一個(gè)異常当辐,運(yùn)行時(shí)系統(tǒng)就會(huì)沿著調(diào)用棧反向?qū)ふ以摦惓5奶幚沓绦颍?dāng)該異常類型與某個(gè)異常處理程序聲明的異常類型一致時(shí)鲤看,系統(tǒng)就將該異常交給它處理缘揪。
如果系統(tǒng)沒能找到合適的異常處理程序,系統(tǒng)將會(huì)終止义桂。
2找筝、異常類型
java提供了兩種處理異常的方式:
- 使用try語句捕獲異常并處理;
- 使用throws關(guān)鍵字列出要拋出的異常類型慷吊,代表在本方法內(nèi)不做處理袖裕,但是調(diào)用該方法的方法必須處理該異常或者繼續(xù)拋出溉瓶。
并不是所有異常都需要顯式處理(這里的處理代表在程序內(nèi)部捕獲或者拋出)急鳄,比如IOException、SQLException等是必須要處理的堰酿,而NullPointerException疾宏、ArithmeticException、IndexOutOfBoundsException等可以不作處理胞锰。
理解這一點(diǎn)灾锯,就要弄清異常的基本分類。
2.1 Checked Exception
這類異常是應(yīng)用程序可以預(yù)見并能夠恢復(fù)的錯(cuò)誤嗅榕,比如顺饮,應(yīng)用程序需要用戶輸入一個(gè)文件名,然后程序?qū)?duì)這個(gè)文件進(jìn)行讀寫操作凌那。假如用戶輸入的文件名不存在兼雄,拋出java.io.FileNotFoundException
,應(yīng)用程序應(yīng)該捕獲這個(gè)異常并提醒用戶。類似這種異常就屬于checked exception也榄。
除了Error、RuntimeException以及兩者的子類南蹂,所有異常都屬于checked exception佃乘。
Error與runtime exception合稱為unchecked exception囱井。
2.2 Error
Error一般來說是應(yīng)用程序外部引起的異常,應(yīng)用程序通常不能預(yù)見并恢復(fù)趣避。比如庞呕,程序順利打開了一個(gè)文件,但是由于硬件或者操作系統(tǒng)故障程帕,不能夠讀取文件中的內(nèi)容住练,程序就會(huì)拋出java.io.IOError
。
Error類型的異常不是必須處理的異常愁拭,當(dāng)然讲逛,你也可以選擇處理它。
2.3 Runtime Exception
runtime exception一般來說是程序內(nèi)部引起的異常岭埠,應(yīng)用程序通常能夠預(yù)見并恢復(fù)盏混。這類異常的出現(xiàn)一般暗示程序存在bug。比如惜论,還是文件操作的例子括饶,由于邏輯錯(cuò)誤,傳入的文件名為空值来涨,程序就會(huì)拋出一個(gè)NullPointerException图焰。
雖然可以讓程序捕獲runtime exception,但更合適的做法是剔除引起這類異常的bug蹦掐。
java的異常類層次圖如下:
3技羔、 異常的捕獲
checked exception必須捕獲,而unchecked exception的捕獲不是必須的卧抗。例如:
import java.io .*;
import java.util.List;
import java.util.ArrayList;
public class ListOfNumbers {
private List<Integer> list;
private static final int SIZE = 10;
public ListOfNumbers() {
list = new ArrayList<Integer>(SIZE);
for (int i = 0; i < SIZE; i++) {
list.add(new Integer(i));
}
}
public void writeList() {
try {
// FileWriter的構(gòu)造方法 throws IOException, checked exception類型藤滥,必須捕獲
PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt"));
for (int i = 0; i < SIZE; i++) {
// get(int)方法throws IndexOutOfBoundsException,RuntimeException的子類社裆,unchecked exception類型拙绊,不是必須要捕獲的
out.println("Value at: " + i + " = " + list.get(i));
}
out.close();
} catch (IOException e) { //捕獲IOException
//...
}
}
}
unchecked exception在一些特殊的情況下也可以選擇捕獲它,比如上面的程序泳秀,現(xiàn)在既要捕獲IOException标沪,也要捕獲IndexOutOfBoundsException,改寫如下:
public void writeList() {
try {
// FileWriter的構(gòu)造方法 throws IOException, checked exception類型嗜傅,必須捕獲
PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt"));
for (int i = 0; i < SIZE; i++) {
// get(int)方法throws IndexOutOfBoundsException金句,RuntimeException的子類,unchecked exception類型吕嘀,不是必須要捕獲的
out.println("Value at: " + i + " = " + list.get(i));
}
out.close();
} catch (IndexOutOfBoundsException e) { //捕獲IndexOutOfBoundsException
//...
} catch (IOException e) { //捕獲IOException
//...
}
}
如果要同時(shí)捕獲的異常存在繼承關(guān)系违寞,即某個(gè)異常時(shí)另一個(gè)異常的子類贞瞒,必須把父類異常寫在子類異常后面,否則會(huì)報(bào)編譯錯(cuò)誤趁曼。但是军浆,無論有幾個(gè)捕獲語句,最終至多會(huì)進(jìn)入一個(gè)catch語句挡闰。
例如:
public class Example {
public void test() throws IOException {
throw new IOException();
}
public static void main(String args[]) {
Example example = new Example();
try {
example.test();
} catch (IOException e) {
System.out.println("捕獲了子類異常");
} catch (Exception e) {
System.out.println("捕獲了父類異常");
}
}
}
上例中瘾敢,IOException是Exception的子類,如果方法拋出了IOException異常尿这,會(huì)進(jìn)入第一個(gè)catch子句,但不會(huì)進(jìn)入第二個(gè)catch語句庆杜;如果拋出的是非IOException的其他Exception子類異常射众,則會(huì)直接進(jìn)入第二個(gè)catch子句。也就是說晃财,不會(huì)同時(shí)進(jìn)入兩個(gè)catch子句叨橱。
從Java SE 7以后,一個(gè)catch塊可以捕獲多個(gè)異常断盛,上面的捕獲語句可簡寫為:
catch (IndexOutOfBoundsException | IOException ex) {
...
}
需要注意的是罗洗,這種情況下,catch的參數(shù)(上例中的“ex”)默認(rèn)是final的钢猛,不能夠在catch塊中對(duì)它再次賦值伙菜。
無論異常是否發(fā)生,try代碼塊退出后命迈,finally代碼塊都會(huì)執(zhí)行贩绕,常常用于釋放資源。例如:
public void writeList() {
PrintWriter out = null;
try {
out = new PrintWriter(new FileWriter("OutFile.txt"));
for (int i = 0; i < SIZE; i++) {
out.println("Value at: " + i + " = " + list.get(i));
}
} catch (IOException e) {
...
} finally { //釋放資源
if (out != null) {
out.close();
}
}
}
有三種情況可導(dǎo)致try代碼塊退出:
-
new FileWriter("OutFile.txt")
拋出IOException -
list.get(i)
拋出IndexOutOfBoundsException - 無異常拋出壶愤,代碼執(zhí)行完畢
無論發(fā)生了上面的那種情況淑倾,運(yùn)行時(shí)系統(tǒng)都會(huì)保證finally代碼塊中的程序執(zhí)行。
需要注意的是征椒,如果在執(zhí)行try-catch代碼塊的時(shí)候JVM退出了娇哆,或者執(zhí)行try-catch代碼塊的線程被中斷或者殺死,或者使用了System.exit()函數(shù)等勃救,finally代碼塊有可能不被執(zhí)行碍讨。
帶有返回值的函數(shù)中使用了try-catch-finally塊,且返回值與是否發(fā)生異常有關(guān)蒙秒,則應(yīng)該避免將返回值寫在finally塊中垄开,因?yàn)闊o論是否會(huì)發(fā)生異常,都會(huì)按照finally塊的返回值税肪,而忽略try-catch任何地方的返回值溉躲。例如:
public int test() {
InputStream in = null;
try {
File f = new File("F:\test.txt");
in = new FileInputStream(f);
return 1;
} catch (IOException e) {
e.printStackTrace();
return 2;
} finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
return 3;
}
}
上例中榜田,無論是否會(huì)拋出IOException,都不會(huì)返回1或2锻梳,只會(huì)返回3箭券。
在try中聲明的一個(gè)或多個(gè)資源,在程序結(jié)束后應(yīng)該關(guān)閉疑枯,通常我們是在finally代碼塊中完成這項(xiàng)工作辩块。
從java 7開始,提供了一種更為簡潔有效的寫法: try-with-resources
語句荆永。
使用形式如下:
static String readFirstLineFromFile(String path) throws IOException {
try (BufferedReader br =
new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
try-with-resources
確保{}內(nèi)的程序執(zhí)行完畢后自動(dòng)關(guān)閉資源废亭,所有實(shí)現(xiàn)了java.lang.AutoCloseable
接口(java 7新增的接口,java.lang.Closeable
的父接口具钥,)的對(duì)象都可以當(dāng)作資源豆村。
4、throw與throws
捕獲異常的前提是有方法拋出了異常骂删。
throw關(guān)鍵字用于在方法體內(nèi)部掌动,發(fā)生錯(cuò)誤的地方拋出異常。如:
public Object pop() {
Object obj;
if (size == 0) { //棧為空宁玫,拋出異常
throw new EmptyStackException();
}
obj = objectAt(size - 1);
setObjectAt(size - 1, null);
size--;
return obj;
}
該方法用于從棧中彈出棧頂元素粗恢,但是,如果棧為空就不能進(jìn)行這項(xiàng)操作欧瘪,所以就會(huì)拋出EmptyStackException異常眷射。
當(dāng)其他方法調(diào)用pop()方法時(shí),就應(yīng)該考慮到pop()可能會(huì)拋出的異常佛掖,如果如果pop()拋出的Unchecked Exception凭迹,可以不做額外的處理;如果pop()拋出的是checked Exception則必須進(jìn)行處理苦囱,可以用try-catch捕獲嗅绸,也可以選擇在本方法內(nèi)不做捕獲,繼續(xù)用throws關(guān)鍵字拋出撕彤,如:
public void callPop() throws EmptyStackException {
//...
pop(); //該方法可能會(huì)拋出EmptyStackException
//...
}
實(shí)際上鱼鸠,一個(gè)異常很多時(shí)候是由于另一個(gè)cause異常引起的,因?yàn)?cause 自身也會(huì)有 cause羹铅,依此類推蚀狰,就形成了鏈?zhǔn)疆惓?Chained Exceptions)。例如:
try {
//...
} catch (IOException e) { //捕獲到IOException時(shí)职员,拋出另一個(gè)異常
throw new SampleException("Other IOException", e);
}
Throwable有一種構(gòu)造函數(shù)可以接受Throwable類型的參數(shù)作為引起該異常的cause麻蹋,Throwable類的initCause(Throwable)、getCause()可以設(shè)置cause信息焊切、獲取cause信息扮授。