異常就是程序生病了恢筝,不處理異常,程序就會(huì)翹翹料睛,終止運(yùn)行丐箩。對(duì)于異常的方法,一定要加上 @exception 文檔注釋恤煞。子類在重寫父類中的抽象方法時(shí)處理的異常一定要比父類中處理的異常多屎勘,也就是說子類 throws 的異常一定只能比父類 throws 的異常少。說的更直白點(diǎn)居扒,就是兒子一定不能比老子懶概漱,再不濟(jì)也要一樣勤快。只要這樣世界才會(huì)不斷進(jìn)步呢喜喂!
異常通常都是交給控制層來處理的犀概,業(yè)務(wù)層和持久層專注于功能實(shí)現(xiàn),在框架中甚至都不需要自行在控制層手工處理異常了夜惭。這是因?yàn)楫惓5脑O(shè)計(jì)之初就是為了幫助程序員方便處理錯(cuò)誤姻灶,所以對(duì)待異常的原則應(yīng)該是把出現(xiàn)問題和處理問題的地方分隔開來。業(yè)務(wù)層和持久層專注于處理業(yè)務(wù)和數(shù)據(jù)存儲(chǔ)的問題诈茧,異常就全部拋給控制層去處理产喉。
為什么要引入異常機(jī)制?
首先明確一個(gè)大前提敢会,運(yùn)行中的程序是一定無法避免出現(xiàn)問題的曾沈。比如斷電了或者機(jī)器硬件壞了,文件找不到或者斷網(wǎng)了鸥昏。那怎么辦呢塞俱? Java 就提供了異常機(jī)制來處理這類問題,通過異常機(jī)制來保證程序的健壯性和可維護(hù)性吏垮。
Java 提供的異常機(jī)制
問題分為 Error 和 Exception 障涯,比如斷電了或者機(jī)器硬件壞了罐旗,導(dǎo)致程序無法正常運(yùn)行,這類屬于 Error 唯蝶,再厲害的程序員也不可能通過寫代碼解決九秀。比如文件找不到或者斷網(wǎng)了,導(dǎo)致程序無法正常運(yùn)行粘我,這類屬于 Exception 鼓蜒。Exception 又分為編譯異常和運(yùn)行異常。編譯異常通常給用戶一個(gè)良好提示征字,運(yùn)行異常在編寫源代碼的過程中要盡量解決掉都弹。異常的命名應(yīng)該是能夠望文生義的,看到名字就能夠猜出異常它的作用匙姜,無論是 java 標(biāo)準(zhǔn)類庫還是自定義的異常類都應(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 屬性是用來描述異常的一段字符串信息。 - 至少兩個(gè)構(gòu)造函數(shù)夹供,一個(gè)空構(gòu)造函數(shù)灵份,一個(gè)有參的構(gòu)造函數(shù)來給私有屬性 detailMessage 賦值。
-
public String getMessage()
方法哮洽,返回的就是異常類的 detailMessage 屬性填渠。 -
public String toString()
Throwable 重寫了其父類 Object 的 toString() 方法,其輸出格式為:異常類名:detailMessage
鸟辅。 -
public void printStackTrace()
用來打印異常棧的信息氛什,方便跟蹤程序異常信息》肆梗控制臺(tái)中打印異常棧信息的順序是:先從異常拋出處的方法開始枪眉,一路回退到它最開始的調(diào)用方。為什么是這樣子的呢再层?要明白贸铜,任何知識(shí)點(diǎn)都不是孤立的堡纬,他們之間一定能夠建立聯(lián)系萨脑。實(shí)現(xiàn)這個(gè)功能其實(shí)很簡單啊隐轩,每一個(gè)線程都有一個(gè)方法調(diào)用棧渤早,所以在遇到拋出異常方法的時(shí)候,在這個(gè)方法棧里面已經(jīng)積壓了很多方法了鹊杖,邊退棧邊打印調(diào)用棧中方法的信息即可悴灵。理解了這一點(diǎn),就理解了為什么會(huì)先從拋出異常方法處首先打印異常了骂蓖,這不就是退棧的過程嗎?此方法還有另外兩個(gè)重載的方法登下,區(qū)別在于不同的參數(shù),接收不同的輸出流對(duì)象被芳,如下public void printStackTrace(PrintWriter s)
和public void printStackTrace(PrintStream s)
方法
編譯異常
5編譯異常中最常見的就是 IOException 了缰贝,這里也只拿這類異常舉例。編譯異常必須顯示的使用 try catch 進(jìn)行預(yù)處理畔濒,否則編譯階段就不給通過。
這其實(shí)是 java 的一種設(shè)計(jì)思想侵状,因?yàn)槌绦蜻\(yùn)行過程中發(fā)生資源不存在問題的可能性非常大,所以 JDK 類庫設(shè)計(jì)者提供的某些方法或者構(gòu)造方法就顯示的使用 throws 關(guān)鍵字事先聲明不處理某種異常趣兄,強(qiáng)迫調(diào)用這種方法的客戶端程序員對(duì)這類異常進(jìn)行預(yù)處理。
運(yùn)行異常
運(yùn)行異常是在程序運(yùn)行過程中遇到不正常情況艇潭,由 JVM 創(chuàng)建并拋出的異常對(duì)象,如果沒有對(duì)此異常進(jìn)行處理的話闯团,該異常對(duì)象一路被拋到 main() 方法中,JVM 就會(huì)自動(dòng)調(diào)用該異常對(duì)象繼承子 Throwable 的public void printStackTrace()
方法打印異常棧信息房交。這種運(yùn)行時(shí)異常是無法在編譯階段檢查出來的伐割,因?yàn)樗耆险Z法規(guī)則候味,只有在程序運(yùn)行時(shí),JVM 才能夠判斷它是否會(huì)出現(xiàn)問題织咧。
比如NullPointerException
和ArithmeticException
就是常見的運(yùn)行時(shí)異常類蟆融。運(yùn)行時(shí)異常才是真正讓人感覺到可怕的事情,在編寫程序的過程中粱玲,即使語法上不要求進(jìn)行異常處理,但是最好顯示的去判斷允青,去處理,程序編寫可能顯得比較麻煩颠锉,但是真正出問題了史汗,就會(huì)發(fā)現(xiàn)一切付出都是值得的琼掠。
異常的處理流程
這里拿運(yùn)行異常舉例淹办,編譯異常是同理的恶复。程序運(yùn)行過程中,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ì)致化的異常處理是為了分化問題章喉,便于對(duì)具體問題做具體分析和處理。要想成為一個(gè)好的程序員囊陡,一定要做到這些。
如果在主方法中使用 throws 聲明不處理異常妥色,這只是騙了編譯器遏片,語法上是通過了,但是主方法是 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ì)通過。
至于 finally 關(guān)鍵字艘包,設(shè)計(jì)之初的本意是用來關(guān)閉資源的,比如輸入輸出流發(fā)生異常卦尊,在 finally 代碼塊中關(guān)閉資源舌厨。無論程序是否發(fā)生異常,finally 的代碼都會(huì)被執(zhí)行裙椭,這是 Java 的設(shè)計(jì)機(jī)制。
try 中發(fā)生異常揉燃,如果異常被 catch 捕獲,則先執(zhí)行catch 的語句正驻,再執(zhí)行 finally 的語句抢腐。如果異常沒有被捕獲姑曙,會(huì)先執(zhí)行 finally 中的代碼氓栈,再拋出此異常婿着,因?yàn)槿绻葤伋隽水惓#?finally 代碼塊的內(nèi)容就無法執(zhí)行了提完。
finally 簡直太牛了丘侠,即使它前面有 return 語句,也會(huì)等到 fianlly 代碼塊的語句執(zhí)行完了后再返回蜗字,但是如果 finally 有 return 語句脂新,就會(huì)覆蓋之前的語句粗梭,可以利用 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)異常断医,但是在處理異常的過程中又發(fā)生了異常,那么此時(shí)本應(yīng)該拋出異常斩启,但是會(huì)先執(zhí)行 finally 代碼塊的內(nèi)容后再拋出異常醉锅。但是如果 finally 代碼塊中有 return 語句,不僅會(huì)覆蓋之前所有的 return 語句硬耍,還會(huì)是的程序就此結(jié)束。之前未來得及處理的異常就這樣被隱藏了此虑。
finally 代碼塊中不要出現(xiàn) return 語句,不要亂寫朦前。上面講到的內(nèi)容只是有可能會(huì)遇到這樣的面試題鹃操,只要知道 finally 中的 return 語句有一個(gè) return 返回棧的概念就可以了,利用這個(gè)概念輔助分析荆隘。
自定義異常類
為什么要使用自定義異常類?Java 異常機(jī)制就是設(shè)計(jì)來處理程序中不正確問題的一種手段晶渠。這些都屬于程序運(yùn)行上的問題燃观,計(jì)算機(jī)是用來解決問題的,在現(xiàn)實(shí)中有很多功能性錯(cuò)誤缆毁,也就是程序運(yùn)行上沒有問題,但是在具體業(yè)務(wù)功能上不符合需求。比如銀行系統(tǒng)中如果用戶取錢大于賬戶余額践啄,這就屬于業(yè)務(wù)功能性錯(cuò)誤沉御。這類問題可以巧妙的利用異常機(jī)制來完成對(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()
方法敌完。