異常就是程序生病了,不處理異常,程序就會(huì)翹翹,終止運(yùn)行。對(duì)于異常的方法,一定要加上 @exception 文檔注釋。子類在重寫父類中的抽象方法時(shí)處理的異常一定要比父類中處理的異常多,也就是說(shuō)子類 throws 的異常一定只能比父類 throws 的異常少。說(shuō)的更直白點(diǎn),就是兒子一定不能比老子懶,再不濟(jì)也要一樣勤快。只要這樣世界才會(huì)不斷進(jìn)步呢!
異常通常都是交給控制層來(lái)處理的,業(yè)務(wù)層和持久層專注于功能實(shí)現(xiàn),在框架中甚至都不需要自行在控制層手工處理異常了。這是因?yàn)楫惓5脑O(shè)計(jì)之初就是為了幫助程序員方便處理錯(cuò)誤,所以對(duì)待異常的原則應(yīng)該是把出現(xiàn)問(wèn)題和處理問(wèn)題的地方分隔開來(lái)。業(yè)務(wù)層和持久層專注于處理業(yè)務(wù)和數(shù)據(jù)存儲(chǔ)的問(wèn)題,異常就全部拋給控制層去處理。
為什么要引入異常機(jī)制?
首先明確一個(gè)大前提,運(yùn)行中的程序是一定無(wú)法避免出現(xiàn)問(wèn)題的。比如斷電了或者機(jī)器硬件壞了,文件找不到或者斷網(wǎng)了。那怎么辦呢? Java 就提供了異常機(jī)制來(lái)處理這類問(wèn)題,通過(guò)異常機(jī)制來(lái)保證程序的健壯性和可維護(hù)性。
Java 提供的異常機(jī)制
問(wèn)題分為 Error 和 Exception ,比如斷電了或者機(jī)器硬件壞了,導(dǎo)致程序無(wú)法正常運(yùn)行,這類屬于 Error ,再厲害的程序員也不可能通過(guò)寫代碼解決。比如文件找不到或者斷網(wǎng)了,導(dǎo)致程序無(wú)法正常運(yùn)行,這類屬于 Exception 。Exception 又分為編譯異常和運(yùn)行異常。編譯異常通常給用戶一個(gè)良好提示,運(yùn)行異常在編寫源代碼的過(guò)程中要盡量解決掉。異常的命名應(yīng)該是能夠望文生義的,看到名字就能夠猜出異常它的作用,無(wú)論是 java 標(biāo)準(zhǔn)類庫(kù)還是自定義的異常類都應(yīng)該遵循此規(guī)則,所有東西都應(yīng)該是這樣命名的啊。比如所有的輸入輸出異常都是 IOException 的子類!
java.lang.Throwable
throwable 是一個(gè)形容詞,表示可拋出的意思。繼承于 java.lang.Object 類,是 Error 和 Exception 的直接父類,它提供了異常類的基本功能。異常類繼承層次結(jié)構(gòu)圖如下:

既然所以異常類都是繼承自 Throwable ,那么 Throwable 擁有的數(shù)據(jù)結(jié)構(gòu)對(duì)于每一個(gè)異常類都是存在的。對(duì)于異常類需要知道的數(shù)據(jù)結(jié)構(gòu)有以下幾個(gè)點(diǎn)。
-
private String detailMessage私有的 String 類型的屬性,detailMessage 屬性是用來(lái)描述異常的一段字符串信息。 - 至少兩個(gè)構(gòu)造函數(shù),一個(gè)空構(gòu)造函數(shù),一個(gè)有參的構(gòu)造函數(shù)來(lái)給私有屬性 detailMessage 賦值。
-
public String getMessage()方法,返回的就是異常類的 detailMessage 屬性。 -
public String toString()Throwable 重寫了其父類 Object 的 toString() 方法,其輸出格式為:異常類名:detailMessage。 -
public void printStackTrace()用來(lái)打印異常棧的信息,方便跟蹤程序異常信息??刂婆_(tái)中打印異常棧信息的順序是:先從異常拋出處的方法開始,一路回退到它最開始的調(diào)用方。為什么是這樣子的呢?要明白,任何知識(shí)點(diǎn)都不是孤立的,他們之間一定能夠建立聯(lián)系。實(shí)現(xiàn)這個(gè)功能其實(shí)很簡(jiǎn)單啊,每一個(gè)線程都有一個(gè)方法調(diào)用棧,所以在遇到拋出異常方法的時(shí)候,在這個(gè)方法棧里面已經(jīng)積壓了很多方法了,邊退棧邊打印調(diào)用棧中方法的信息即可。理解了這一點(diǎn),就理解了為什么會(huì)先從拋出異常方法處首先打印異常了,這不就是退棧的過(guò)程嗎?此方法還有另外兩個(gè)重載的方法,區(qū)別在于不同的參數(shù),接收不同的輸出流對(duì)象,如下public void printStackTrace(PrintWriter s)和public void printStackTrace(PrintStream s)方法
編譯異常
5編譯異常中最常見的就是 IOException 了,這里也只拿這類異常舉例。編譯異常必須顯示的使用 try catch 進(jìn)行預(yù)處理,否則編譯階段就不給通過(guò)。
這其實(shí)是 java 的一種設(shè)計(jì)思想,因?yàn)槌绦蜻\(yùn)行過(guò)程中發(fā)生資源不存在問(wèn)題的可能性非常大,所以 JDK 類庫(kù)設(shè)計(jì)者提供的某些方法或者構(gòu)造方法就顯示的使用 throws 關(guān)鍵字事先聲明不處理某種異常,強(qiáng)迫調(diào)用這種方法的客戶端程序員對(duì)這類異常進(jìn)行預(yù)處理。
運(yùn)行異常
運(yùn)行異常是在程序運(yùn)行過(guò)程中遇到不正常情況,由 JVM 創(chuàng)建并拋出的異常對(duì)象,如果沒有對(duì)此異常進(jìn)行處理的話,該異常對(duì)象一路被拋到 main() 方法中,JVM 就會(huì)自動(dòng)調(diào)用該異常對(duì)象繼承子 Throwable 的public void printStackTrace()方法打印異常棧信息。這種運(yùn)行時(shí)異常是無(wú)法在編譯階段檢查出來(lái)的,因?yàn)樗耆险Z(yǔ)法規(guī)則,只有在程序運(yùn)行時(shí),JVM 才能夠判斷它是否會(huì)出現(xiàn)問(wèn)題。
比如NullPointerException和ArithmeticException就是常見的運(yùn)行時(shí)異常類。運(yùn)行時(shí)異常才是真正讓人感覺到可怕的事情,在編寫程序的過(guò)程中,即使語(yǔ)法上不要求進(jìn)行異常處理,但是最好顯示的去判斷,去處理,程序編寫可能顯得比較麻煩,但是真正出問(wèn)題了,就會(huì)發(fā)現(xiàn)一切付出都是值得的。
異常的處理流程
這里拿運(yùn)行異常舉例,編譯異常是同理的。程序運(yùn)行過(guò)程中,JVM 發(fā)現(xiàn)不正確情況,就在 new 出一個(gè)異常對(duì)象,并給它的 detailMessage 屬性賦值。然后檢查此處是否有 try catch 捕獲了對(duì)應(yīng)的異常對(duì)象,如果有則進(jìn)入到 catch 代碼段,如果沒有則查看此方法是否使用 throws 關(guān)鍵字聲明不處理異常,如果有則到調(diào)用此方法的方法中進(jìn)行同樣的流程處理。如果都沒有,JVM 就會(huì)拋出此異常,程序被迫中斷,控制臺(tái)打印出相應(yīng)的異常信息。
捕獲異常的原則是必須要盡可能的細(xì)化,catch 代碼塊要呈金字塔鋪開。做更細(xì)致化的異常處理是為了分化問(wèn)題,便于對(duì)具體問(wèn)題做具體分析和處理。要想成為一個(gè)好的程序員,一定要做到這些。
如果在主方法中使用 throws 聲明不處理異常,這只是騙了編譯器,語(yǔ)法上是通過(guò)了,但是主方法是 JVM 調(diào)用的,相當(dāng)于還是拋給了 JVM ,該發(fā)生的異常還是會(huì)發(fā)生,程序該中斷還是會(huì)中斷。
throws 和 throw 以及 finally
throws 用在方法聲明后面,后面跟的是一個(gè)或多個(gè)異常類,表示不處理異常。throw 用在方法內(nèi)部,后面跟的是一個(gè)異常對(duì)象,表明此處拋出一個(gè)異常對(duì)象。如果一個(gè)方法中 throw 出一個(gè)異常對(duì)象,此方法就必須用 throws 聲明不處理此異常,那就會(huì)拋給調(diào)用此方法的方法去處理。或者在此方法內(nèi)部用 try catch 捕獲,否則編譯階段都不會(huì)通過(guò)。
至于 finally 關(guān)鍵字,設(shè)計(jì)之初的本意是用來(lái)關(guān)閉資源的,比如輸入輸出流發(fā)生異常,在 finally 代碼塊中關(guān)閉資源。無(wú)論程序是否發(fā)生異常,finally 的代碼都會(huì)被執(zhí)行,這是 Java 的設(shè)計(jì)機(jī)制。
try 中發(fā)生異常,如果異常被 catch 捕獲,則先執(zhí)行catch 的語(yǔ)句,再執(zhí)行 finally 的語(yǔ)句。如果異常沒有被捕獲,會(huì)先執(zhí)行 finally 中的代碼,再拋出此異常,因?yàn)槿绻葤伋隽水惓?,?finally 代碼塊的內(nèi)容就無(wú)法執(zhí)行了。
finally 簡(jiǎn)直太牛了,即使它前面有 return 語(yǔ)句,也會(huì)等到 fianlly 代碼塊的語(yǔ)句執(zhí)行完了后再返回,但是如果 finally 有 return 語(yǔ)句,就會(huì)覆蓋之前的語(yǔ)句,可以利用 return 返回棧的概念分析。實(shí)例代碼如下:
public static int f() {
try {
System.out.println("try"+ 5/0);
return 1;
}catch(Exception e){
return 3;
}finally {
return 2;
}
}//此方法返回 2
如果 catch 捕獲了相應(yīng)異常,但是在處理異常的過(guò)程中又發(fā)生了異常,那么此時(shí)本應(yīng)該拋出異常,但是會(huì)先執(zhí)行 finally 代碼塊的內(nèi)容后再拋出異常。但是如果 finally 代碼塊中有 return 語(yǔ)句,不僅會(huì)覆蓋之前所有的 return 語(yǔ)句,還會(huì)是的程序就此結(jié)束。之前未來(lái)得及處理的異常就這樣被隱藏了。
finally 代碼塊中不要出現(xiàn) return 語(yǔ)句,不要亂寫。上面講到的內(nèi)容只是有可能會(huì)遇到這樣的面試題,只要知道 finally 中的 return 語(yǔ)句有一個(gè) return 返回棧的概念就可以了,利用這個(gè)概念輔助分析。
自定義異常類
為什么要使用自定義異常類?Java 異常機(jī)制就是設(shè)計(jì)來(lái)處理程序中不正確問(wèn)題的一種手段。這些都屬于程序運(yùn)行上的問(wèn)題,計(jì)算機(jī)是用來(lái)解決問(wèn)題的,在現(xiàn)實(shí)中有很多功能性錯(cuò)誤,也就是程序運(yùn)行上沒有問(wèn)題,但是在具體業(yè)務(wù)功能上不符合需求。比如銀行系統(tǒng)中如果用戶取錢大于賬戶余額,這就屬于業(yè)務(wù)功能性錯(cuò)誤。這類問(wèn)題可以巧妙的利用異常機(jī)制來(lái)完成對(duì)她的處理。
如何自定義異常?自定義異常首先要做的當(dāng)然是繼承 java.lang.Exception 類,不然你要自己重新寫一個(gè)嗎?然后提供一個(gè)空構(gòu)造函數(shù)和一個(gè)有參的構(gòu)造函數(shù),在其內(nèi)部調(diào)用 super 關(guān)鍵字給父類的私有屬性 detailMessage 屬性賦值。重寫 toString() 方法,輸出格式為:類名:detailMessage。根據(jù)具體需求還可以重寫printStackTrace()方法。