Java語(yǔ)法總結(jié) - 異常
軟件開(kāi)發(fā)中一個(gè)古老的說(shuō)法是:80%的工作使用20%的時(shí)間。80%是指檢查和處理錯(cuò)誤所付出的努力。在許多語(yǔ)言中,編寫(xiě)檢查和處理錯(cuò)誤的程序代碼很乏味,并使應(yīng)用程序代碼變得冗長(zhǎng)。原因之一就是它們的錯(cuò)誤處理方式不是語(yǔ)言的一部分。盡管如此,錯(cuò)誤檢測(cè)和處理仍然是任何健壯應(yīng)用程序最重要的組成部分。
Java提供了一種很好的機(jī)制,用強(qiáng)制規(guī)定的形式來(lái)消除錯(cuò)誤處理過(guò)程中隨心所欲的因素:異常處理。它的優(yōu)秀之處在于不用編寫(xiě)特殊代碼檢測(cè)返回值就能很容易地檢測(cè)錯(cuò)誤。而且它讓我們把異常處理代碼明確地與異常產(chǎn)生代碼分開(kāi),代碼變得更有條理。異常處理也是Java中唯一正式的錯(cuò)誤報(bào)告機(jī)制。
第一部分 異常
1、拋出異常。所有的標(biāo)準(zhǔn)異常類都有兩個(gè)構(gòu)造器:一個(gè)是缺省構(gòu)造器,一個(gè)是帶參數(shù)的構(gòu)造器,以便把相關(guān)信息放入異常對(duì)象中。
throw new NullPointerException();
throw new NullPointerException("t = null");
2、如果有一個(gè)或者多個(gè)catch塊,則它們必須緊跟在try塊之后,而且這些catch塊必須互相緊跟著,不能有其他任何代碼。C++沒(méi)有這樣的限制,所以C++的異常處理處理不好就會(huì)寫(xiě)得很亂,拋來(lái)拋去的。
3、使用try塊把可能出現(xiàn)異常的代碼包含在其中,這么做的好處是:處理某種指定的異常的代碼,只需編寫(xiě)一次。作業(yè)沒(méi)寫(xiě)完的同學(xué)到走廊罰站去,這符合我們處理問(wèn)題的方式,不用挨個(gè)地告訴。
4、無(wú)論是否拋出異常,finally塊封裝的代碼總能夠在try塊之后的某點(diǎn)執(zhí)行。
例子:
try {
return ;
}
finally{
System.out.print("You can't jump out of my hand!");
}
甚至你在try塊內(nèi)用return語(yǔ)句想跳過(guò)去都不可以!finally內(nèi)的輸出語(yǔ)句還是執(zhí)行了!別想逃出我的手掌心!
5、catch塊和finally塊是可選的,你可以只使用try。但是這么做有意思嗎?
6、推卸責(zé)任。Java允許你推卸責(zé)任,沒(méi)有必要從相應(yīng)的try塊為每個(gè)可能的異常都編寫(xiě)catch子句。Java2類庫(kù)中很多方法都會(huì)拋出異常,就是是把異常處理的權(quán)限交給了我們用戶。畢竟,Java不知道你的自行車(chē)被偷了之后,你會(huì)去報(bào)案還是會(huì)忍氣吞聲自認(rèn)倒霉,或者偷別人的自行車(chē)。我們需要這種處理異常的自由度。
7、調(diào)用棧。調(diào)用棧是程序執(zhí)行以訪問(wèn)當(dāng)前方法的方法鏈。被調(diào)用的最后一個(gè)方法在棧的頂部,它將被最先執(zhí)行完畢,然后彈出;第一個(gè)調(diào)用方法位于底部,也就是main函數(shù)。在catch子句中使用printStackTrace()方法打調(diào)用棧信息是比較常用的定位異常的方法。printStackTrace()繼承自Throwable。
8、異常的傳播。在一個(gè)方法A中,如果一個(gè)異常沒(méi)有得到處理,它就會(huì)被自動(dòng)拋到調(diào)用A方法的B方法中。如果B方法也沒(méi)有處理這個(gè)異常,他就會(huì)被繼續(xù)依次向上拋,直到main方法。如果main也沒(méi)有理會(huì)它,那么異常將導(dǎo)致JVM停止,程序就中止了。你被同學(xué)揍了,先去告訴老師。老師不理你你就去告訴教導(dǎo)處主任,教導(dǎo)處主任也不管那只能告訴校長(zhǎng),校長(zhǎng)還不管!沒(méi)有比他更大的了,于是你崩潰了,學(xué)業(yè)中止了……下面這段程序記錄了悲慘的輟學(xué)歷史:
class ExceptionDemo {
static void student() throws Exception{
teacher();
}
static void teacher() throws Exception{
schoolmaster();
}
static void schoolmaster() throws Exception{
throw new Exception();
}
public static void main(String[] args) {
try {
student();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
輸出結(jié)果是:
java.lang.Exception
at ExceptionDemo.schoolmaster(ExceptionDemo.java:9)
at ExceptionDemo.teacher(ExceptionDemo.java:6)
at ExceptionDemo.student(ExceptionDemo.java:3)
at ExceptionDemo.main(ExceptionDemo.java:13)
可以看出函數(shù)的調(diào)用棧,一級(jí)一級(jí)地哭訴……
9、異常的層次結(jié)構(gòu)及Error。
Object
Throwable
Error Exception
Throwable繼承自O(shè)bject,Error和Exception繼承自Throwable。Error比較特殊,它對(duì)應(yīng)于我們常說(shuō)的不可抗拒的外力,房屋中介的合同上總有一條,如遇不可抗拒的外力本合同中止,返還乙方押金。我不安地問(wèn):不可抗拒的外力指什么?中介回答:比如戰(zhàn)爭(zhēng)、彗星撞擊地球等。對(duì)Java來(lái)說(shuō)Error是指JVM內(nèi)存耗盡等這類不是程序錯(cuò)誤或者其他事情引起的特殊情況。一般地,程序不能從Error中恢復(fù),因此你可以能眼睜睜地看著程序崩潰而不必責(zé)怪自己。嚴(yán)格來(lái)講,Error不是異常,因?yàn)樗皇抢^承自Exception。
10、誰(shuí)之錯(cuò)?一般地,異常不是我們程序員的錯(cuò),不是程序設(shè)計(jì)上的缺陷。比如讀取一個(gè)重要文件,這個(gè)文件被用戶誤刪了;正上著網(wǎng)呢,網(wǎng)線被用戶的寵物咬斷了。為了程序的健壯性,我們盡量考慮出現(xiàn)可能性大的異常,并處理,但我們不能窮盡。
11、異常的捕獲之一。catch子句的參數(shù)是某種類型異常的對(duì)象,如果拋出的異常是該參數(shù)的子類,那么這個(gè)異常將被它捕獲。也就是說(shuō)被拋出的異常不會(huì)精確地尋找最匹配的捕獲者(catch子句),只要是它的繼承結(jié)構(gòu)的直系上層就可以捕獲它。
按照這個(gè)邏輯,catch(Exception e) 不就能捕獲所有的異常嗎?事實(shí)上,確實(shí)如此。 但是一般地,不建議使用這種一站式的異常處理。因?yàn)檫@樣就丟失了具體的異常信息,不能為某個(gè)具體的異常編寫(xiě)相應(yīng)的異常處理代碼,失去了異常處理的意義。從哲學(xué)角度來(lái)講,具體問(wèn)題要具體分析,能治百病的萬(wàn)能藥一般都是無(wú)效的保健品。
Java在此處為什么這么設(shè)計(jì)呢?因?yàn)橛辛硪环N機(jī)制的存在,請(qǐng)看下條分解。
12、異常的捕獲之二。當(dāng)拋出一個(gè)異常時(shí),Java試圖尋找一個(gè)能捕獲它的catch子句,如果沒(méi)找到就會(huì)沿著棧向下傳播。這個(gè)過(guò)程就是異常匹配。Java規(guī)定:最具體的異常處理程序必須總是放在更普通異常處理程序的前面。這條規(guī)定再合理不過(guò)了,試想如果把catch(Exception e)放在最上面,那么下面的catch子句豈不是永遠(yuǎn)不能執(zhí)行了?如果你非要把更普遍的異常處理放在前面,對(duì)不起,通不過(guò)編譯!雖然編譯器不會(huì)這樣報(bào)錯(cuò):“It is so stupid to do like that!”……
13、捕獲或聲明規(guī)則。如果在一個(gè)方法中拋出異常,你有兩個(gè)選擇:要么用catch子句捕獲所有的異常,要么在方法中聲明將要拋出的異常,否則編譯器不會(huì)讓你得逞的。
方案一:處理異常
void ex(){
try{
throw new Exception();
} catch (Exception e) {
e.printStackTrace();
}
}
方案二:拋出去
void ex() throws Exception{
throw new Exception();
}
比較一下行數(shù)就知道了,在代碼的世界里推卸責(zé)任也是那么簡(jiǎn)單,一個(gè)throws關(guān)鍵字包含了多少人生哲理啊……現(xiàn)實(shí)生活中我們有很多角色,兒女、父母、學(xué)生、老師、老板、員工……每個(gè)人都占了幾條。可是你能盡到所有責(zé)任嗎?按照古代的孝道,父母尚在人世就不能遠(yuǎn)行。各種責(zé)任是有矛盾的,顧此失彼啊。
但是這條規(guī)則有個(gè)特例。一個(gè)繼承自Exception名為RuntimeException的子類,也就是運(yùn)行時(shí)異常,不受上述規(guī)則的限制。下面的代碼完全能編譯,只不過(guò)調(diào)用之后在運(yùn)行時(shí)會(huì)拋出異常。
void ex(){
throw new RuntimeException();
}
14、throw和thrwos關(guān)鍵字。throw用在方法體中拋出異常,后面是一個(gè)具體的異常對(duì)象。throws用在方法參數(shù)列表括號(hào)的后面,用來(lái)聲明此方法會(huì)拋出的異常種類,后面跟著一個(gè)異常類。
15、非檢查異常。RuntimeException、Error以及它們的子類都是非檢查異常,不要求定義或處理非檢查異常。Java2類庫(kù)中有很多方法拋出檢查異常,因此會(huì)常常編寫(xiě)異常處理程序來(lái)處理不是你編寫(xiě)的方法產(chǎn)生的異常。這種機(jī)制強(qiáng)制開(kāi)發(fā)人員處理錯(cuò)誤,使得Java程序更加健壯,安全。
16、自定義異常類型。覺(jué)得現(xiàn)有的異常無(wú)法描述你想拋出的異常,ok!Java允許你自定義異常類型,只需要繼承Exception或者它的子類,然后換上有個(gè)性的名字。
class NotEnoughMoney extends Exception {
public NotEnoughMoney() {}
public NotEnoughMoney(String msg) { super(msg); }
}
希望大家在生活里不要拋出類似的異常。
17、重新拋出異常。一個(gè)很無(wú)聊的話題,純粹的語(yǔ)法研究,實(shí)際意義不大。當(dāng)catch子句捕獲到異常之后可以重新拋出,那么它所在的方法必須聲明該異常。
void ex() throws Exception{
try {
throw new Exception();
}
catch (Exception mex) {
throw me;
}
}
18、異常處理機(jī)制的效率。待補(bǔ)充……
19、終止與恢復(fù)模型。異常處理理論上有兩種模型:
一、終止模型。錯(cuò)誤很關(guān)鍵且無(wú)法挽回,再執(zhí)行下去也沒(méi)意義,只能中止。“羅密歐,我們分手吧!”“好吧,朱麗葉!”
二、恢復(fù)模型。經(jīng)過(guò)錯(cuò)誤修正重新嘗試調(diào)用原來(lái)出問(wèn)題的方法。“羅密歐,我們分手吧!”“朱麗葉,我錯(cuò)了!請(qǐng)?jiān)僭徫乙淮伟桑?#8221;“好的,再原諒你最后一次!”
顯然我們更喜歡恢復(fù)模型,但在實(shí)際中,這種模式是不易實(shí)現(xiàn)和維護(hù)的。
例子:用戶輸入了非法的字符,分別按照兩種模式處理
一、終止模型。輸出出錯(cuò)信息而已,一旦用戶手一抖眼一花你的代碼就崩潰了
double number;
String sNumber = "";
try {
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
sNumber = bf.readLine();
number = Double.parseDouble(sNumber);
} catch (IOException ioe) {
System.err.println("some IOException");
} catch (NumberFormatException nfe) {
System.err.println(sNumber + " is Not a legal number!");
}
//System.out.println(number);
二、恢復(fù)模型。小樣!不輸入正確的數(shù)據(jù)類型就別想離開(kāi)!
double number = 0;
String sNumber = "";
while(true){
try {
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
sNumber = bf.readLine();
number = Double.parseDouble(sNumber);
break; //如果代碼能執(zhí)行到這一行,就說(shuō)明沒(méi)有拋出異常
} catch (IOException ioe) {
System.err.println("some IOException");
} catch (NumberFormatException nfe) {
System.err.println(sNumber + " is Not a legal number!");
}
}
System.out.println(number);
直到用戶輸入正確的信息才會(huì)被該代碼放過(guò)。這是一種簡(jiǎn)單的恢復(fù)模型的實(shí)現(xiàn),挺耐看的,我很喜歡!
20、try、catch、finally內(nèi)變量的作用域和可見(jiàn)性。
在try塊內(nèi)定義的變量,它在catch或者finally塊內(nèi)都是無(wú)法訪問(wèn)到的,并且在整個(gè)異常處理語(yǔ)句之外也是不可見(jiàn)的。
補(bǔ)充一點(diǎn)初始化:第一個(gè)例中最后一句被注釋掉了。number是在運(yùn)行時(shí)由用戶輸入而初始化的,但是在編譯時(shí)刻并沒(méi)有初始化,編譯器會(huì)抱怨的。
21、輸出異常信息。捕捉到異常之后,通常我們會(huì)輸出相關(guān)的信息,以便更加明確異常。
catch (Exception mex) {
System.err.println("caught a exception!");
}
用標(biāo)準(zhǔn)錯(cuò)誤流System.err比System.out要好。因?yàn)镾ystem.out也許會(huì)被重定向,System.err則不會(huì)。
22、更高級(jí)的話題我會(huì)補(bǔ)充上的,但是我的肚子拋出了Hungry異常,我必須catch然后調(diào)用eat()方法補(bǔ)充能量。昨晚的魷魚(yú)蓋澆飯很好吃……
返回索引頁(yè)