Java的基本理念是“結(jié)構(gòu)不佳的代碼不能運行”W;眙帷!S绶蕖心剥!
??????大成若缺,其用不弊背桐。
???????大盈若沖优烧,其用不窮。
????在這個世界不可能存在完美的東西链峭,不管完美的思維有多么縝密畦娄,細(xì)心,我們都不可能考慮所有的因素弊仪,這就是所謂的智者千慮必有一失熙卡。同樣的道理,計算機的世界也是不完美的励饵,異常情況隨時都會發(fā)生驳癌,我們所需要做的就是避免那些能夠避免的異常,處理那些不能避免的異常役听。這里我將記錄如何利用異常還程序一個“完美世界”颓鲜。
一表窘、為什么要使用異常
????首先我們可以明確一點就是異常的處理機制可以確保我們程序的健壯性,提高系統(tǒng)可用率灾杰。雖然我們不是特別喜歡看到它蚊丐,但是我們不能不承認(rèn)它的地位熙参,作用艳吠。
????有異常就說明程序存在問題,有助于我們及時改正孽椰。在我們的程序設(shè)計當(dāng)做昭娩,任何時候任何地方因為任何原因都有可能會出現(xiàn)異常,在沒有異常機制的時候我們是這樣處理的:通過函數(shù)的返回值來判斷是否發(fā)生了異常(這個返回值通常是已經(jīng)約定好了的)黍匾,調(diào)用該函數(shù)的程序負(fù)責(zé)檢查并且分析返回值栏渺。
雖然可以解決異常問題,但是這樣做存在幾個缺陷:
1锐涯、 容易混淆磕诊。如果約定返回值為-11111時表示出現(xiàn)異常,那么當(dāng)程序最后的計算結(jié)果真的為-1111呢纹腌?
2霎终、 代碼可讀性差。將異常處理代碼和程序代碼混淆在一起將會降低代碼的可讀性升薯。
3莱褒、 由調(diào)用函數(shù)來分析異常,這要求程序員對庫函數(shù)有很深的了解涎劈。
在OO中提供的異常處理機制是提供代碼健壯的強有力的方式广凸。使用異常機制它能夠降低錯誤處理代碼的復(fù)雜度,如果不使用異常蛛枚,那么就必須檢查特定的錯誤谅海,并在程序中的許多地方去處理它,而如果使用異常蹦浦,那就不必在方法調(diào)用處進行檢查胁赢,因為異常機制將保證能夠捕獲這個錯誤,并且白筹,只需在一個地方處理錯誤智末,即所謂的異常處理程序中。這種方式不僅節(jié)約代碼徒河,而且把“概述在正常執(zhí)行過程中做什么事”的代碼和“出了問題怎么辦”的代碼相分離系馆。總之顽照,與以前的錯誤處理方法相比由蘑,異常機制使代碼的閱讀闽寡、編寫和調(diào)試工作更加井井有條。(摘自《Think in java 》)尼酿。
????在初學(xué)時爷狈,總是聽老師說把有可能出錯的地方記得加異常處理,剛剛開始還不明白裳擎,有時候還覺得只是多此一舉涎永,現(xiàn)在隨著自己的不斷深入,代碼編寫多了鹿响,漸漸明白了異常是非常重要的羡微。
二、基本定義
????在《Think in java》中是這樣定義異常的:異常情形是指阻止當(dāng)前方法或者作用域繼續(xù)執(zhí)行的問題惶我。在這里一定要明確一點:異常代碼某種程度的錯誤妈倔,盡管Java有異常處理機制,但是我們不能以“正吵窆保”的眼光來看待異常盯蝴,異常處理機制的原因就是告訴你:這里可能會或者已經(jīng)產(chǎn)生了錯誤,您的程序出現(xiàn)了不正常的情況听怕,可能會導(dǎo)致程序失斉跬Α!
????那么什么時候才會出現(xiàn)異常呢叉跛?
????只有在你當(dāng)前的環(huán)境下程序無法正常運行下去松忍,也就是說程序已經(jīng)無法來正確解決問題了,這時它所就會從當(dāng)前環(huán)境中跳出筷厘,并拋出異常鸣峭。拋出異常后,它首先會做幾件事酥艳。首先摊溶,它會使用new創(chuàng)建一個異常對象,然后在產(chǎn)生異常的位置終止程序充石,并且從當(dāng)前環(huán)境中彈出對異常對象的引用莫换,這時。異常處理機制就會接管程序骤铃,并開始尋找一個恰當(dāng)?shù)牡胤絹砝^續(xù)執(zhí)行程序拉岁,這個恰當(dāng)?shù)牡胤骄褪钱惓L幚沓绦颍娜蝿?wù)就是將程序從錯誤狀態(tài)恢復(fù)惰爬,以使程序要么換一種方法執(zhí)行喊暖,要么繼續(xù)執(zhí)行下去。
????總的來說異常處理機制就是當(dāng)程序發(fā)生異常時撕瞧,它強制終止程序運行陵叽,記錄異常信息并將這些信息反饋給我們狞尔,由我們來確定是否處理異常。
三巩掺、異常體系
???????java為我們提供了非常完美的異常處理機制偏序,使得我們可以更加專心于我們的程序,在使用異常之前我們需要了解它的體系結(jié)構(gòu):
????從上面這幅圖可以看出胖替,Throwable是java語言中所有錯誤和異常的超類(萬物即可拋)研儒。它有兩個子類:Error、Exception刊殉。
????其中Error為錯誤殉摔,是程序無法處理的州胳,如OutOfMemoryError记焊、ThreadDeath等,出現(xiàn)這種情況你唯一能做的就是聽之任之栓撞,交由JVM來處理遍膜,不過JVM在大多數(shù)情況下會選擇終止線程。
????而Exception是程序可以處理的異常瓤湘。它又分為兩種CheckedException(受撿異常)瓢颅,一種是UncheckedException(不受檢異常)。其中CheckException發(fā)生在編譯階段弛说,必須要使用try…catch(或者throws)否則編譯不通過挽懦。而UncheckedException發(fā)生在運行期,具有不確定性木人,主要是由于程序的邏輯問題所引起的信柿,難以排查,我們一般都需要縱觀全局才能夠發(fā)現(xiàn)這類的異常錯誤醒第,所以在程序設(shè)計中我們需要認(rèn)真考慮渔嚷,好好寫代碼,盡量處理異常稠曼,即使產(chǎn)生了異常形病,也能盡量保證程序朝著有利方向發(fā)展。
????所以:對于可恢復(fù)的條件使用被檢查的異常(CheckedException)霞幅,對于程序錯誤(言外之意不可恢復(fù)漠吻,大錯已經(jīng)釀成)使用運行時異常(RuntimeException)。
四司恳、異常使用
????在網(wǎng)上看了這樣一個搞笑的話:世界上最真情的相依途乃,是你在try我在catch。無論你發(fā)神馬脾氣抵赢,我都默默承受欺劳,靜靜處理唧取。
????對于初學(xué)者來說異常就是try…catch,(鄙人剛剛接觸時也是這么認(rèn)為的划提,碰到異常就是try…catch)枫弟。個人感覺try…catch確實是用的最多也是最實用的。
????在異常中try快包含著可能出現(xiàn)異常的代碼塊鹏往,catch塊捕獲異常后對異常進行處理淡诗。先看如下實例:
????這是段非常簡單的程序,用于讀取D盤目錄下的exceptionText.txt文件伊履,同時讀取其中的內(nèi)容韩容、輸出。首先D盤沒有該文件唐瀑,運行程序結(jié)果如下:
從這個結(jié)果我們可以看出這些:
1群凶、當(dāng)程序遇到異常時會終止程序的運行(即后面的代碼不在執(zhí)行),控制權(quán)交由異常處理機制處理哄辣。
2请梢、catch捕捉異常后,執(zhí)行里面的函數(shù)力穗。
當(dāng)我們在D盤目錄下新建一個exceptionTest.txt文件后毅弧,運行程序結(jié)果如下:
????11111是該文件中的內(nèi)容。從這個運行結(jié)果可以得出這個結(jié)果:不論程序是否發(fā)生異常当窗,finally代碼塊總是會執(zhí)行够坐。所以finally一般用來關(guān)閉資源。
????在這里我們在看如下程序:
???程序運行結(jié)果:
???各位請注意這個異常信息和上面的異常信息錯誤崖面,為了看得更加清楚元咙,我將他們列在一起:
???在這里我們發(fā)現(xiàn)兩個異常之間存在如下區(qū)別:第二個異常信息多了Exception in thread?"main",這顯示了出現(xiàn)異常信息的位置嘶朱。
????在這里可以得到如下結(jié)論:若程序中顯示的聲明了某個異常蛾坯,則拋出異常時不會顯示出處,若程序中沒有顯示的聲明某個異常疏遏,當(dāng)拋出異常時脉课,系統(tǒng)會顯示異常的出處。
五财异、自定義異常
? ? Java確實給我們提供了非常多的異常倘零,但是異常體系是不可能預(yù)見所有的希望加以報告的錯誤,所以Java允許我們自定義異常來表現(xiàn)程序中可能會遇到的特定問題戳寸,總之就是一句話:我們不必拘泥于Java中已有的異常類型呈驶。
????Java自定義異常的使用要經(jīng)歷如下四個步驟:
1、定義一個類繼承Throwable或其子類疫鹊。
???????2袖瞻、添加構(gòu)造方法(當(dāng)然也可以不用添加司致,使用默認(rèn)構(gòu)造方法)。
???????3聋迎、在某個方法類拋出該異常脂矫。
???????4、捕捉該異常霉晕。?
運行結(jié)果:
六庭再、異常鏈
????在設(shè)計模式中有一個叫做責(zé)任鏈模式,該模式是將多個對象鏈接成一條鏈牺堰,客戶端的請求沿著這條鏈傳遞直到被接收拄轻、處理。同樣Java異常機制也提供了這樣一條鏈:異常鏈伟葫。
????我們知道每遇到一個異常信息恨搓,我們都需要進行try…catch,一個還好,如果出現(xiàn)多個異常呢扒俯?分類處理肯定會比較麻煩奶卓,那就一個Exception解決所有的異常吧一疯。這樣確實是可以撼玄,但是這樣處理勢必會導(dǎo)致后面的維護難度增加。最好的辦法就是將這些異常信息封裝墩邀,然后捕獲我們的封裝類即可掌猛。
????誠然在應(yīng)用程序中,我們有時候不僅僅只需要封裝異常眉睹,更需要傳遞荔茬。怎么傳遞?throws!竹海!binge慕蔚,正確!斋配!但是如果僅僅只用throws拋出異常孔飒,那么你的封裝類,怎么辦艰争?坏瞄?
????我們有兩種方式處理異常,一是throws拋出交給上級處理甩卓,二是try…catch做具體處理鸠匀。但是這個與上面有什么關(guān)聯(lián)呢?try…catch的catch塊我們可以不需要做任何處理逾柿,僅僅只用throw這個關(guān)鍵字將我們封裝異常信息主動拋出來缀棍。然后在通過關(guān)鍵字throws繼續(xù)拋出該方法異常宅此。它的上層也可以做這樣的處理,以此類推就會產(chǎn)生一條由異常構(gòu)成的異常鏈爬范。
????通過使用異常鏈诽凌,我們可以提高代碼的可理解性、系統(tǒng)的可維護性和友好性坦敌。
????同理侣诵,我們有時候在捕獲一個異常后拋出另一個異常信息,并且希望將原始的異常信息也保持起來狱窘,這個時候也需要使用異常鏈杜顺。
????在異常鏈的使用中,throw拋出的是一個新的異常信息蘸炸,這樣勢必會導(dǎo)致原有的異常信息丟失躬络,如何保持?在Throwable及其子類中的構(gòu)造器中都可以接受一個cause參數(shù)搭儒,該參數(shù)保存了原有的異常信息穷当,通過getCause()就可以獲取該原始異常信息。
????語法:
? ? 示例:
????運行結(jié)果:
???如果在程序中,去掉e淹禾,也就是:throw new MyException("文件沒有找到--02");
???那么異常信息就保存不了馁菜,運行結(jié)果如下:
七、異常的使用誤區(qū)
???????首先我們先看如下示例:該實例能夠反映java異常的不正確使用(其實這也是我剛剛學(xué)Java時寫的代碼)A宀怼汪疮!
1、-----------1
????對于這個try…catch塊毁习,我想他的真正目的是捕獲SQL的異常智嚷,但是這個try塊是不是包含了太多的信息了。這是我們?yōu)榱送祽卸B(yǎng)成的代碼壞習(xí)慣纺且。有些人喜歡將一大塊的代碼全部包含在一個try塊里面盏道,因為這樣省事,反正有異常它就會拋出载碌,而不愿意花時間來分析這個大代碼塊有那幾塊會產(chǎn)生異常猜嘱,產(chǎn)生什么類型的異常,反正就是一簍子全部搞定恐仑。這就想我們出去旅游將所有的東西全部裝進一個箱子里面泉坐,而不是分類來裝,雖不知裝進去容易裳仆,找出來難巴笕谩!!纯丸!所有對于一個異常塊偏形,我們應(yīng)該仔細(xì)分清楚每塊的拋出異常,因為一個大代碼塊有太多的地方會出現(xiàn)異常了觉鼻。
結(jié)論一:盡可能的減小try塊?∨ぁ!坠陈!
2萨惑、-----------2
????在這里你發(fā)現(xiàn)了什么?異常改變了運行流程3鸱庸蔼!不錯就是異常改變了程序運行流程。如果該程序發(fā)生了異常那么conn.close();?out.close();是不可能執(zhí)行得到的贮匕,這樣勢必會導(dǎo)致資源不能釋放掉姐仅。所以如果程序用到了文件、Socket刻盐、JDBC連接之類的資源掏膏,即使遇到了異常,我們也要確保能夠正確釋放占用的資源敦锌。這里finally就有用武之地了:不管是否出現(xiàn)了異常馒疹,finally總是有機會運行的,所以finally用于釋放資源是再適合不過了供屉。
結(jié)論二:保證所有資源都被正確釋放行冰。充分運用finally關(guān)鍵詞。
3伶丐、-----------3
????對于這個代碼我想大部分人都是這樣處理的,(LZ也是)疯特。使用這樣代碼的人都有這樣一個心理哗魂,一個catch解決所有異常,這樣是可以漓雅,但是不推薦录别!為什么!首先我們需要明白catch塊所表示是它預(yù)期會出現(xiàn)何種異常邻吞,并且需要做何種處理组题,而使用Exception就表示他要處理所有的異常信息,但是這樣做有什么意義呢抱冷?
????這里我們再來看看上面的程序?qū)嵗蘖校茱@然它可能需要拋出兩個異常信息,SQLException和IOException。所以一個catch處理兩個截然不同的Exception明顯的不合適赵讯。如果用兩個catch盈咳,一個處理SQLException、一個處理IOException就好多了边翼。所以:
結(jié)論三:catch語句應(yīng)當(dāng)盡量指定具體的異常類型鱼响,而不應(yīng)該指定涵蓋范圍太廣的Exception類。 不要一個Exception試圖處理所有可能出現(xiàn)的異常组底。
4丈积、-----------4
????這個就問題多多了,我敢保證幾乎所有的人都這么使用過债鸡。這里涉及到了兩個問題桶癣,一是,捕獲了異常不做處理娘锁,二是異常信息不夠明確牙寞。
4.1、捕獲異常不做處理莫秆,就是我們所謂的丟棄異常间雀。
我們都知道異常意味著程序出現(xiàn)了不可預(yù)期的問題,程序它希望我們能夠做出處理來拯救它镊屎,但是你呢惹挟?一句ex.printStackTrace()搞定,這是多么的不負(fù)責(zé)任對程序的異常情況不理不顧缝驳。雖然這樣在調(diào)試可能會有一定的幫助连锯,但是調(diào)試階段結(jié)束后呢?不是一句ex.printStackTrace()就可以搞定所有的事情的用狱!
那么怎么改進呢运怖?有四種選擇:
1、處理異常夏伊。對所發(fā)生的的異常進行一番處理摇展,如修正錯誤、提醒溺忧。再次申明ex.printStackTrace()算不上已經(jīng)“處理好了異秤搅”.
2、重新拋出異常鲁森。既然你認(rèn)為你沒有能力處理該異常祟滴,那么你就盡情向上拋吧!8韪取垄懂!
3、封裝異常。這是LZ認(rèn)為最好的處理方法埠偿,對異常信息進行分類透罢,然后進行封裝處理。
4冠蒋、不要捕獲異常羽圃。
???????
4.2、異常信息不明確抖剿。
我想對于這樣的:java.io.FileNotFoundException: ………信息除了我們IT人沒有幾個人看得懂和想看吧朽寞!所以在出現(xiàn)異常后,我們最好能夠提供一些文字信息斩郎,例如當(dāng)前正在執(zhí)行的類脑融、方法和其他狀態(tài)信息,包括以一種更適合閱讀的方式整理和組織printStackTrace提供的信息缩宜。起碼我公司是需要將異常信息所在的類肘迎、方法锻煌、何種異常都需要記錄在日志文件中的父晶。
所以:
結(jié)論四:既然捕獲了異常揩慕,就要對它進行適當(dāng)?shù)奶幚怼2灰东@異常之后又把它丟棄,不予理睬。 不要做一個不負(fù)責(zé)的人。
???????結(jié)論五:在異常處理模塊中提供適量的錯誤原因信息,組織錯誤信息使其易于理解和閱讀系奉。
對于異常還有以下幾個注意地方:
不要在finally塊中處理返回值萌踱。
不要在構(gòu)造函數(shù)中拋出異常扔涧。
八、try…catch、throw、throws
????在這里主要是區(qū)分throw和throws稳诚。
????throws是方法拋出異常桑逝。在方法聲明中茬暇,如果添加了throws子句,表示該方法即將拋出異常寡喝,異常的處理交由它的調(diào)用者糙俗,至于調(diào)用者任何處理則不是它的責(zé)任范圍內(nèi)的了。所以如果一個方法會有異常發(fā)生時预鬓,但是又不想處理或者沒有能力處理巧骚,就使用throws吧!
? ? 而throw是語句拋出異常珊皿。它不可以單獨使用网缝,要么與try…catch配套使用,要么與throws配套使用蟋定。
九粉臊、總結(jié)
應(yīng)該在下列情況下使用異常。
???1驶兜、在恰當(dāng)?shù)募墑e處理問題(在知道該如何處理異常的情況下才捕獲異常)扼仲。
???2、解決問題并且重新調(diào)用產(chǎn)生異常的方法抄淑。
???3屠凶、進行少許修補,然后繞過異常發(fā)生的地方繼續(xù)執(zhí)行肆资。
???4矗愧、用別的數(shù)據(jù)進行計算,以代替方法預(yù)計會返回的值郑原。
???5唉韭、把當(dāng)前運行環(huán)境下能做的事情盡量做完。然后把相同(不同)的異常重新拋到更高層犯犁。
???6属愤、終止程序。
???7酸役、進行簡化住诸。
???8、讓類庫和程序更加安全涣澡。(這既是在為調(diào)試做短期投資贱呐,也是在為程序的健壯做長期投資)