Java的基本理念是“結(jié)構(gòu)不佳的代碼不能運(yùn)行”!?。。?!
??????大成若缺,其用不弊。
???????大盈若沖,其用不窮。
????在這個(gè)世界不可能存在完美的東西,不管完美的思維有多么縝密,細(xì)心,我們都不可能考慮所有的因素,這就是所謂的智者千慮必有一失。同樣的道理,計(jì)算機(jī)的世界也是不完美的,異常情況隨時(shí)都會(huì)發(fā)生,我們所需要做的就是避免那些能夠避免的異常,處理那些不能避免的異常。這里我將記錄如何利用異常還程序一個(gè)“完美世界”。
一、為什么要使用異常
????首先我們可以明確一點(diǎn)就是異常的處理機(jī)制可以確保我們程序的健壯性,提高系統(tǒng)可用率。雖然我們不是特別喜歡看到它,但是我們不能不承認(rèn)它的地位,作用。
????有異常就說(shuō)明程序存在問(wèn)題,有助于我們及時(shí)改正。在我們的程序設(shè)計(jì)當(dāng)做,任何時(shí)候任何地方因?yàn)槿魏卧蚨加锌赡軙?huì)出現(xiàn)異常,在沒(méi)有異常機(jī)制的時(shí)候我們是這樣處理的:通過(guò)函數(shù)的返回值來(lái)判斷是否發(fā)生了異常(這個(gè)返回值通常是已經(jīng)約定好了的),調(diào)用該函數(shù)的程序負(fù)責(zé)檢查并且分析返回值。
雖然可以解決異常問(wèn)題,但是這樣做存在幾個(gè)缺陷:
1、 容易混淆。如果約定返回值為-11111時(shí)表示出現(xiàn)異常,那么當(dāng)程序最后的計(jì)算結(jié)果真的為-1111呢?
2、 代碼可讀性差。將異常處理代碼和程序代碼混淆在一起將會(huì)降低代碼的可讀性。
3、 由調(diào)用函數(shù)來(lái)分析異常,這要求程序員對(duì)庫(kù)函數(shù)有很深的了解。
在OO中提供的異常處理機(jī)制是提供代碼健壯的強(qiáng)有力的方式。使用異常機(jī)制它能夠降低錯(cuò)誤處理代碼的復(fù)雜度,如果不使用異常,那么就必須檢查特定的錯(cuò)誤,并在程序中的許多地方去處理它,而如果使用異常,那就不必在方法調(diào)用處進(jìn)行檢查,因?yàn)楫惓C(jī)制將保證能夠捕獲這個(gè)錯(cuò)誤,并且,只需在一個(gè)地方處理錯(cuò)誤,即所謂的異常處理程序中。這種方式不僅節(jié)約代碼,而且把“概述在正常執(zhí)行過(guò)程中做什么事”的代碼和“出了問(wèn)題怎么辦”的代碼相分離??傊?,與以前的錯(cuò)誤處理方法相比,異常機(jī)制使代碼的閱讀、編寫和調(diào)試工作更加井井有條。(摘自《Think in java 》)。
????在初學(xué)時(shí),總是聽(tīng)老師說(shuō)把有可能出錯(cuò)的地方記得加異常處理,剛剛開始還不明白,有時(shí)候還覺(jué)得只是多此一舉,現(xiàn)在隨著自己的不斷深入,代碼編寫多了,漸漸明白了異常是非常重要的。
二、基本定義
????在《Think in java》中是這樣定義異常的:異常情形是指阻止當(dāng)前方法或者作用域繼續(xù)執(zhí)行的問(wèn)題。在這里一定要明確一點(diǎn):異常代碼某種程度的錯(cuò)誤,盡管Java有異常處理機(jī)制,但是我們不能以“正?!钡难酃鈦?lái)看待異常,異常處理機(jī)制的原因就是告訴你:這里可能會(huì)或者已經(jīng)產(chǎn)生了錯(cuò)誤,您的程序出現(xiàn)了不正常的情況,可能會(huì)導(dǎo)致程序失敗!
????那么什么時(shí)候才會(huì)出現(xiàn)異常呢?
????只有在你當(dāng)前的環(huán)境下程序無(wú)法正常運(yùn)行下去,也就是說(shuō)程序已經(jīng)無(wú)法來(lái)正確解決問(wèn)題了,這時(shí)它所就會(huì)從當(dāng)前環(huán)境中跳出,并拋出異常。拋出異常后,它首先會(huì)做幾件事。首先,它會(huì)使用new創(chuàng)建一個(gè)異常對(duì)象,然后在產(chǎn)生異常的位置終止程序,并且從當(dāng)前環(huán)境中彈出對(duì)異常對(duì)象的引用,這時(shí)。異常處理機(jī)制就會(huì)接管程序,并開始尋找一個(gè)恰當(dāng)?shù)牡胤絹?lái)繼續(xù)執(zhí)行程序,這個(gè)恰當(dāng)?shù)牡胤骄褪钱惓L幚沓绦?,它的任?wù)就是將程序從錯(cuò)誤狀態(tài)恢復(fù),以使程序要么換一種方法執(zhí)行,要么繼續(xù)執(zhí)行下去。
????總的來(lái)說(shuō)異常處理機(jī)制就是當(dāng)程序發(fā)生異常時(shí),它強(qiáng)制終止程序運(yùn)行,記錄異常信息并將這些信息反饋給我們,由我們來(lái)確定是否處理異常。
三、異常體系
???????java為我們提供了非常完美的異常處理機(jī)制,使得我們可以更加專心于我們的程序,在使用異常之前我們需要了解它的體系結(jié)構(gòu):
????從上面這幅圖可以看出,Throwable是java語(yǔ)言中所有錯(cuò)誤和異常的超類(萬(wàn)物即可拋)。它有兩個(gè)子類:Error、Exception。
????其中Error為錯(cuò)誤,是程序無(wú)法處理的,如OutOfMemoryError、ThreadDeath等,出現(xiàn)這種情況你唯一能做的就是聽(tīng)之任之,交由JVM來(lái)處理,不過(guò)JVM在大多數(shù)情況下會(huì)選擇終止線程。
????而Exception是程序可以處理的異常。它又分為兩種CheckedException(受撿異常),一種是UncheckedException(不受檢異常)。其中CheckException發(fā)生在編譯階段,必須要使用try…catch(或者throws)否則編譯不通過(guò)。而UncheckedException發(fā)生在運(yùn)行期,具有不確定性,主要是由于程序的邏輯問(wèn)題所引起的,難以排查,我們一般都需要縱觀全局才能夠發(fā)現(xiàn)這類的異常錯(cuò)誤,所以在程序設(shè)計(jì)中我們需要認(rèn)真考慮,好好寫代碼,盡量處理異常,即使產(chǎn)生了異常,也能盡量保證程序朝著有利方向發(fā)展。
????所以:對(duì)于可恢復(fù)的條件使用被檢查的異常(CheckedException),對(duì)于程序錯(cuò)誤(言外之意不可恢復(fù),大錯(cuò)已經(jīng)釀成)使用運(yùn)行時(shí)異常(RuntimeException)。
四、異常使用
????在網(wǎng)上看了這樣一個(gè)搞笑的話:世界上最真情的相依,是你在try我在catch。無(wú)論你發(fā)神馬脾氣,我都默默承受,靜靜處理。
????對(duì)于初學(xué)者來(lái)說(shuō)異常就是try…catch,(鄙人剛剛接觸時(shí)也是這么認(rèn)為的,碰到異常就是try…catch)。個(gè)人感覺(jué)try…catch確實(shí)是用的最多也是最實(shí)用的。
????在異常中try快包含著可能出現(xiàn)異常的代碼塊,catch塊捕獲異常后對(duì)異常進(jìn)行處理。先看如下實(shí)例:
????這是段非常簡(jiǎn)單的程序,用于讀取D盤目錄下的exceptionText.txt文件,同時(shí)讀取其中的內(nèi)容、輸出。首先D盤沒(méi)有該文件,運(yùn)行程序結(jié)果如下:
從這個(gè)結(jié)果我們可以看出這些:
1、當(dāng)程序遇到異常時(shí)會(huì)終止程序的運(yùn)行(即后面的代碼不在執(zhí)行),控制權(quán)交由異常處理機(jī)制處理。
2、catch捕捉異常后,執(zhí)行里面的函數(shù)。
當(dāng)我們?cè)贒盤目錄下新建一個(gè)exceptionTest.txt文件后,運(yùn)行程序結(jié)果如下:
????11111是該文件中的內(nèi)容。從這個(gè)運(yùn)行結(jié)果可以得出這個(gè)結(jié)果:不論程序是否發(fā)生異常,finally代碼塊總是會(huì)執(zhí)行。所以finally一般用來(lái)關(guān)閉資源。
????在這里我們?cè)诳慈缦鲁绦颍?/p>
???程序運(yùn)行結(jié)果:
???各位請(qǐng)注意這個(gè)異常信息和上面的異常信息錯(cuò)誤,為了看得更加清楚,我將他們列在一起:
???在這里我們發(fā)現(xiàn)兩個(gè)異常之間存在如下區(qū)別:第二個(gè)異常信息多了Exception in thread?"main",這顯示了出現(xiàn)異常信息的位置。
????在這里可以得到如下結(jié)論:若程序中顯示的聲明了某個(gè)異常,則拋出異常時(shí)不會(huì)顯示出處,若程序中沒(méi)有顯示的聲明某個(gè)異常,當(dāng)拋出異常時(shí),系統(tǒng)會(huì)顯示異常的出處。
五、自定義異常
? ? Java確實(shí)給我們提供了非常多的異常,但是異常體系是不可能預(yù)見(jiàn)所有的希望加以報(bào)告的錯(cuò)誤,所以Java允許我們自定義異常來(lái)表現(xiàn)程序中可能會(huì)遇到的特定問(wèn)題,總之就是一句話:我們不必拘泥于Java中已有的異常類型。
????Java自定義異常的使用要經(jīng)歷如下四個(gè)步驟:
1、定義一個(gè)類繼承Throwable或其子類。
???????2、添加構(gòu)造方法(當(dāng)然也可以不用添加,使用默認(rèn)構(gòu)造方法)。
???????3、在某個(gè)方法類拋出該異常。
???????4、捕捉該異常。?
運(yùn)行結(jié)果:
六、異常鏈
????在設(shè)計(jì)模式中有一個(gè)叫做責(zé)任鏈模式,該模式是將多個(gè)對(duì)象鏈接成一條鏈,客戶端的請(qǐng)求沿著這條鏈傳遞直到被接收、處理。同樣Java異常機(jī)制也提供了這樣一條鏈:異常鏈。
????我們知道每遇到一個(gè)異常信息,我們都需要進(jìn)行try…catch,一個(gè)還好,如果出現(xiàn)多個(gè)異常呢?分類處理肯定會(huì)比較麻煩,那就一個(gè)Exception解決所有的異常吧。這樣確實(shí)是可以,但是這樣處理勢(shì)必會(huì)導(dǎo)致后面的維護(hù)難度增加。最好的辦法就是將這些異常信息封裝,然后捕獲我們的封裝類即可。
????誠(chéng)然在應(yīng)用程序中,我們有時(shí)候不僅僅只需要封裝異常,更需要傳遞。怎么傳遞?throws!!binge,正確??!但是如果僅僅只用throws拋出異常,那么你的封裝類,怎么辦??
????我們有兩種方式處理異常,一是throws拋出交給上級(jí)處理,二是try…catch做具體處理。但是這個(gè)與上面有什么關(guān)聯(lián)呢?try…catch的catch塊我們可以不需要做任何處理,僅僅只用throw這個(gè)關(guān)鍵字將我們封裝異常信息主動(dòng)拋出來(lái)。然后在通過(guò)關(guān)鍵字throws繼續(xù)拋出該方法異常。它的上層也可以做這樣的處理,以此類推就會(huì)產(chǎn)生一條由異常構(gòu)成的異常鏈。
????通過(guò)使用異常鏈,我們可以提高代碼的可理解性、系統(tǒng)的可維護(hù)性和友好性。
????同理,我們有時(shí)候在捕獲一個(gè)異常后拋出另一個(gè)異常信息,并且希望將原始的異常信息也保持起來(lái),這個(gè)時(shí)候也需要使用異常鏈。
????在異常鏈的使用中,throw拋出的是一個(gè)新的異常信息,這樣勢(shì)必會(huì)導(dǎo)致原有的異常信息丟失,如何保持?在Throwable及其子類中的構(gòu)造器中都可以接受一個(gè)cause參數(shù),該參數(shù)保存了原有的異常信息,通過(guò)getCause()就可以獲取該原始異常信息。
????語(yǔ)法:
? ? 示例:
????運(yùn)行結(jié)果:
???如果在程序中,去掉e,也就是:throw new MyException("文件沒(méi)有找到--02");
???那么異常信息就保存不了,運(yùn)行結(jié)果如下:
七、異常的使用誤區(qū)
???????首先我們先看如下示例:該實(shí)例能夠反映java異常的不正確使用(其實(shí)這也是我剛剛學(xué)Java時(shí)寫的代碼)??!
1、-----------1
????對(duì)于這個(gè)try…catch塊,我想他的真正目的是捕獲SQL的異常,但是這個(gè)try塊是不是包含了太多的信息了。這是我們?yōu)榱送祽卸B(yǎng)成的代碼壞習(xí)慣。有些人喜歡將一大塊的代碼全部包含在一個(gè)try塊里面,因?yàn)檫@樣省事,反正有異常它就會(huì)拋出,而不愿意花時(shí)間來(lái)分析這個(gè)大代碼塊有那幾塊會(huì)產(chǎn)生異常,產(chǎn)生什么類型的異常,反正就是一簍子全部搞定。這就想我們出去旅游將所有的東西全部裝進(jìn)一個(gè)箱子里面,而不是分類來(lái)裝,雖不知裝進(jìn)去容易,找出來(lái)難?。。?!所有對(duì)于一個(gè)異常塊,我們應(yīng)該仔細(xì)分清楚每塊的拋出異常,因?yàn)橐粋€(gè)大代碼塊有太多的地方會(huì)出現(xiàn)異常了。
結(jié)論一:盡可能的減小try塊?。?!
2、-----------2
????在這里你發(fā)現(xiàn)了什么?異常改變了運(yùn)行流程??!不錯(cuò)就是異常改變了程序運(yùn)行流程。如果該程序發(fā)生了異常那么conn.close();?out.close();是不可能執(zhí)行得到的,這樣勢(shì)必會(huì)導(dǎo)致資源不能釋放掉。所以如果程序用到了文件、Socket、JDBC連接之類的資源,即使遇到了異常,我們也要確保能夠正確釋放占用的資源。這里finally就有用武之地了:不管是否出現(xiàn)了異常,finally總是有機(jī)會(huì)運(yùn)行的,所以finally用于釋放資源是再適合不過(guò)了。
結(jié)論二:保證所有資源都被正確釋放。充分運(yùn)用finally關(guān)鍵詞。
3、-----------3
????對(duì)于這個(gè)代碼我想大部分人都是這樣處理的,(LZ也是)。使用這樣代碼的人都有這樣一個(gè)心理,一個(gè)catch解決所有異常,這樣是可以,但是不推薦!為什么!首先我們需要明白catch塊所表示是它預(yù)期會(huì)出現(xiàn)何種異常,并且需要做何種處理,而使用Exception就表示他要處理所有的異常信息,但是這樣做有什么意義呢?
????這里我們?cè)賮?lái)看看上面的程序?qū)嵗?,很顯然它可能需要拋出兩個(gè)異常信息,SQLException和IOException。所以一個(gè)catch處理兩個(gè)截然不同的Exception明顯的不合適。如果用兩個(gè)catch,一個(gè)處理SQLException、一個(gè)處理IOException就好多了。所以:
結(jié)論三:catch語(yǔ)句應(yīng)當(dāng)盡量指定具體的異常類型,而不應(yīng)該指定涵蓋范圍太廣的Exception類。 不要一個(gè)Exception試圖處理所有可能出現(xiàn)的異常。
4、-----------4
????這個(gè)就問(wèn)題多多了,我敢保證幾乎所有的人都這么使用過(guò)。這里涉及到了兩個(gè)問(wèn)題,一是,捕獲了異常不做處理,二是異常信息不夠明確。
4.1、捕獲異常不做處理,就是我們所謂的丟棄異常。
我們都知道異常意味著程序出現(xiàn)了不可預(yù)期的問(wèn)題,程序它希望我們能夠做出處理來(lái)拯救它,但是你呢?一句ex.printStackTrace()搞定,這是多么的不負(fù)責(zé)任對(duì)程序的異常情況不理不顧。雖然這樣在調(diào)試可能會(huì)有一定的幫助,但是調(diào)試階段結(jié)束后呢?不是一句ex.printStackTrace()就可以搞定所有的事情的!
那么怎么改進(jìn)呢?有四種選擇:
1、處理異常。對(duì)所發(fā)生的的異常進(jìn)行一番處理,如修正錯(cuò)誤、提醒。再次申明ex.printStackTrace()算不上已經(jīng)“處理好了異?!?
2、重新拋出異常。既然你認(rèn)為你沒(méi)有能力處理該異常,那么你就盡情向上拋吧?。。?/p>
3、封裝異常。這是LZ認(rèn)為最好的處理方法,對(duì)異常信息進(jìn)行分類,然后進(jìn)行封裝處理。
4、不要捕獲異常。
???????
4.2、異常信息不明確。
我想對(duì)于這樣的:java.io.FileNotFoundException: ………信息除了我們IT人沒(méi)有幾個(gè)人看得懂和想看吧!所以在出現(xiàn)異常后,我們最好能夠提供一些文字信息,例如當(dāng)前正在執(zhí)行的類、方法和其他狀態(tài)信息,包括以一種更適合閱讀的方式整理和組織printStackTrace提供的信息。起碼我公司是需要將異常信息所在的類、方法、何種異常都需要記錄在日志文件中的。
所以:
結(jié)論四:既然捕獲了異常,就要對(duì)它進(jìn)行適當(dāng)?shù)奶幚怼2灰东@異常之后又把它丟棄,不予理睬。 不要做一個(gè)不負(fù)責(zé)的人。
???????結(jié)論五:在異常處理模塊中提供適量的錯(cuò)誤原因信息,組織錯(cuò)誤信息使其易于理解和閱讀。
對(duì)于異常還有以下幾個(gè)注意地方:
不要在finally塊中處理返回值。
不要在構(gòu)造函數(shù)中拋出異常。
八、try…catch、throw、throws
????在這里主要是區(qū)分throw和throws。
????throws是方法拋出異常。在方法聲明中,如果添加了throws子句,表示該方法即將拋出異常,異常的處理交由它的調(diào)用者,至于調(diào)用者任何處理則不是它的責(zé)任范圍內(nèi)的了。所以如果一個(gè)方法會(huì)有異常發(fā)生時(shí),但是又不想處理或者沒(méi)有能力處理,就使用throws吧!
? ? 而throw是語(yǔ)句拋出異常。它不可以單獨(dú)使用,要么與try…catch配套使用,要么與throws配套使用。
九、總結(jié)
應(yīng)該在下列情況下使用異常。
???1、在恰當(dāng)?shù)募?jí)別處理問(wèn)題(在知道該如何處理異常的情況下才捕獲異常)。
???2、解決問(wèn)題并且重新調(diào)用產(chǎn)生異常的方法。
???3、進(jìn)行少許修補(bǔ),然后繞過(guò)異常發(fā)生的地方繼續(xù)執(zhí)行。
???4、用別的數(shù)據(jù)進(jìn)行計(jì)算,以代替方法預(yù)計(jì)會(huì)返回的值。
???5、把當(dāng)前運(yùn)行環(huán)境下能做的事情盡量做完。然后把相同(不同)的異常重新拋到更高層。
???6、終止程序。
???7、進(jìn)行簡(jiǎn)化。
???8、讓類庫(kù)和程序更加安全。(這既是在為調(diào)試做短期投資,也是在為程序的健壯做長(zhǎng)期投資)