顧名思義,通俗來(lái)說(shuō)異常就是指,那些產(chǎn)生在我們本來(lái)斟酌和設(shè)定的計(jì)劃以外的意外情況。
生活中總是會(huì)存在各種突發(fā)情況,如果沒(méi)有做好準(zhǔn)備,就讓人措手不及。
你和朋友約好了明天1起去登山,半道上忽然烏云蔽日,下起了磅礴大雨。這就是所謂的異常情況。
你1下子傻眼了,然后看見(jiàn)朋友淡定的從背包里取出1件雨衣穿上,淫笑著看著你。這就是對(duì)異常的處理。
對(duì)1個(gè)OO程序猿來(lái)說(shuō),所做的工作就是:將需要處理的現(xiàn)實(shí)生活中的復(fù)雜問(wèn)題,抽象出來(lái)編寫(xiě)成為程序。
既然現(xiàn)實(shí)生活中總是存在著各種突然的異常情況,那末對(duì)應(yīng)其抽象出的代碼,自然也是存在這樣的風(fēng)險(xiǎn)的。
所以常常說(shuō):要編寫(xiě)1個(gè)完善的程序其實(shí)不只是簡(jiǎn)簡(jiǎn)單單的把功能實(shí)現(xiàn),還要讓程序具有處理在運(yùn)行中可能出現(xiàn)的各種意外情況的能力。
這就是所謂的異常的使用。
這就是Java當(dāng)中異常體系的結(jié)構(gòu)構(gòu)成,從圖中我們可以提取到的信息就是:
1、Java中定義的所有異常類(lèi)都是內(nèi)置類(lèi)Throwable的子類(lèi)。
2、Java中的異常通常被分為兩大類(lèi):Error和Exception:
3、Exception最多見(jiàn)的兩種異常類(lèi)型分別是:
要理解Java中的異常使用,首先要明白幾個(gè)關(guān)于異常處理的工具 - 異常處理關(guān)鍵字的使用。
1、throw:用以在方法內(nèi)部拋出指定類(lèi)型的異常。
2、throws:用以聲明 1個(gè)方法可能產(chǎn)生的異常(通常都是編譯時(shí)檢測(cè)異常)有哪些。
用try語(yǔ)句塊包括可能產(chǎn)生異常的代碼,catch用于捕獲產(chǎn)生的異常,并在catch語(yǔ)句塊中定義對(duì)捕獲到的異常的處理方式。
特點(diǎn)是被try語(yǔ)句塊包括的內(nèi)容中,是不是真的產(chǎn)生了異常。程序終究都將履行finally語(yǔ)句塊當(dāng)中的內(nèi)容。
通經(jīng)常使用于對(duì)資源的釋放操作,例如:通過(guò)JDBC連接數(shù)據(jù)庫(kù)等情況。
了解Java中異常類(lèi)的實(shí)際利用之前,應(yīng)當(dāng)先了解兩個(gè)概念,用以對(duì)最經(jīng)常使用的異常做1個(gè)分類(lèi):
1、編譯時(shí)被檢測(cè)異常:
只要是Exception和其子類(lèi)都是,除特殊子類(lèi)RuntimeException體系。
所謂的編譯時(shí)被檢測(cè)異常也就是指在程序的編譯器就會(huì)進(jìn)行檢測(cè)的異常分類(lèi)。
也就是說(shuō),如果1個(gè)方法拋出了1個(gè)編譯時(shí)檢測(cè)異常,Java則要求我們必須進(jìn)行處理。
既:通過(guò)throws對(duì)異常進(jìn)行聲明處理 或是 通過(guò)try-catch對(duì)異常進(jìn)行捕獲處理。
如果程序編譯時(shí)檢測(cè)到該類(lèi)異常沒(méi)有被進(jìn)行任何處理,那末編譯器則會(huì)報(bào)出1個(gè)編譯毛病。
上面代碼中的ClassNotFoundException就是1種編譯時(shí)檢測(cè)異常,這個(gè)異常是由Class類(lèi)當(dāng)中的forName方法所拋出并聲明的。
如果我們?cè)谑褂迷摲椒〞r(shí)沒(méi)有對(duì)異常進(jìn)行處理:聲明或捕獲,那末該程序就會(huì)編譯失敗。
通過(guò)這個(gè)例子想要說(shuō)明的是:編譯時(shí)被檢測(cè)異常通常都是指那些“可以被我們預(yù)感”的異常情況。
正例如:我們通過(guò)Class.forName是想要獲得指定類(lèi)的字節(jié)碼文件對(duì)象,所以我們自然也能夠預(yù)感可能會(huì)存在:
與我們傳入的類(lèi)名參數(shù)所對(duì)應(yīng)的類(lèi)字節(jié)碼文件對(duì)象不存在,查找不到的情況。
既然這類(lèi)意外情況是可以被預(yù)感的,那自然就應(yīng)當(dāng)針對(duì)其制定1些應(yīng)對(duì)方案。
2、編譯時(shí)不檢測(cè)異常(運(yùn)行時(shí)異常):
就是指Exception下的子類(lèi)RuntimeException和其子類(lèi)。
通常這類(lèi)問(wèn)題的產(chǎn)生,會(huì)致使程序功能沒(méi)法繼續(xù)、運(yùn)算沒(méi)法進(jìn)行等情況產(chǎn)生;
但這類(lèi)異常更多是由于調(diào)用者的緣由或引發(fā)了內(nèi)部狀態(tài)的改變而致使的。
所以針對(duì)這類(lèi)異常,編譯器不要求我們處理,可以直接編譯通過(guò)。
而在運(yùn)行時(shí),讓調(diào)用者調(diào)用時(shí)的程序強(qiáng)迫停止,從而讓調(diào)用者對(duì)本身的代碼進(jìn)行修正。
曾看到過(guò)1道面試題:列出你實(shí)際開(kāi)發(fā)中最多見(jiàn)的5個(gè)運(yùn)行時(shí)異常,就我自己而言,如果硬要說(shuō)出5個(gè),那多是:
NullPointerException(空指針異常)、IndexOutOfBoundsException(角標(biāo)越界異常)、ArithmeticException(異常運(yùn)算條件異常)
ClassCastException(類(lèi)型轉(zhuǎn)換異常)、IllegalArgumentException(非法參數(shù)異常)
同時(shí)也能夠看到,雖然“division”方法可能引發(fā)異常,但由于是運(yùn)行時(shí)異常,所以即便不做任何異常處理,程序任然能夠通過(guò)編譯。
但當(dāng)該類(lèi)型的異常真的產(chǎn)生的時(shí)候,調(diào)用者運(yùn)行的程序就會(huì)直接停止運(yùn)行,并輸出相干的異常信息。
通過(guò)自定義異常理解檢測(cè)異常和非檢測(cè)異常
前面我們說(shuō)到的都是Java本身已封裝好提供給我們的1些異常類(lèi)。由此我們可以看到,秉持于“萬(wàn)物皆對(duì)象”的思想,Java中的異常實(shí)際上也是1種對(duì)象。
所以自然的,除Java本身提供的異常類(lèi)以外,我們也能夠根據(jù)自己的需求定義自己的異常類(lèi)。
這里我想通過(guò)比較有趣的簡(jiǎn)單的自定義異常,結(jié)合自己的理解,總結(jié)1下Java當(dāng)中檢測(cè)異常和非檢測(cè)異常的使用。
1、編譯時(shí)檢測(cè)異常
對(duì)編譯時(shí)異常,我的理解就是:所有你可以預(yù)感、并且能夠做出應(yīng)對(duì)的意外狀態(tài),都應(yīng)當(dāng)通過(guò)編譯時(shí)檢測(cè)異常的定義的方式進(jìn)行處理。
舉個(gè)例子來(lái)講:假定我們開(kāi)了1家小餐館,除開(kāi)正常營(yíng)業(yè)的流程以外。自然可能產(chǎn)生1些意外狀態(tài),例如:
菜里不謹(jǐn)慎出現(xiàn)了蟲(chóng)子,出現(xiàn)了頭發(fā);或是餐館突然停電之類(lèi)的狀態(tài)。這些狀態(tài)是每一個(gè)經(jīng)營(yíng)餐館的人事前都應(yīng)當(dāng)斟酌到的情況。
既然我們已斟酌到了這些意外情況產(chǎn)生的可能性,那末自然就應(yīng)當(dāng)針對(duì)這些狀態(tài)做出應(yīng)對(duì)的方案。所以代碼多是這樣的:
1、首先,定義兩個(gè)編譯時(shí)檢測(cè)異常類(lèi),菜品異常和停電異常:
2、然后我們?cè)诓蛷d類(lèi)當(dāng)中的營(yíng)業(yè)方法當(dāng)中做出了聲明,如果出現(xiàn)“菜里有蟲(chóng)”或“菜里有頭發(fā)的問(wèn)題”,我們就用thorw拋出1個(gè)菜品異常;如果“停電”,就拋出停電異常。
3、但是,由于我們拋出這1類(lèi)異常是由于想告知餐廳的相干人員,在餐廳營(yíng)業(yè)后,可能會(huì)出現(xiàn)這些意外情況。所以還應(yīng)當(dāng)通過(guò)throws告知他們:營(yíng)業(yè)可能會(huì)出現(xiàn)這些意外情況。
4、餐廳相干人員接到了聲明。因而制定了方案,當(dāng)餐廳開(kāi)始營(yíng)業(yè)后。如果出現(xiàn)了菜品異常,請(qǐng)為客人換1盤(pán)菜或退款;如果出現(xiàn)停電異常,請(qǐng)啟動(dòng)店里自備的發(fā)機(jī)電。
2、運(yùn)行時(shí)異常
對(duì)運(yùn)行時(shí)異常的使用,我個(gè)人覺(jué)得最經(jīng)常使用的情況有兩種:
第1、編譯時(shí)檢測(cè)異經(jīng)常使用于定義那些我們可以提供“友好的解決方案”的情況。那末針對(duì)另外1些狀態(tài),多是我們沒(méi)法很好的進(jìn)行解決的。
遇到這類(lèi)情況,我們可能希望采取1些“強(qiáng)迫手段”,那就是直接讓你的程序停止運(yùn)行。這時(shí)候,就能夠使用運(yùn)行時(shí)異常。
第2、如果對(duì)異常處理后,又引發(fā)1連串的毛病的“連鎖反應(yīng)”的時(shí)候。
我們先來(lái)看1下第1種使用使用情況是怎樣樣的。例如說(shuō):
我們?cè)谏厦娴牟蛷d的例子中,餐廳即便出現(xiàn)菜品異常或停電異常這1類(lèi)意外情況。
但針對(duì)這1類(lèi)的意外情況,我們是能夠提供較為妥善的解決方案的。
而通過(guò)我們提供的針對(duì)這些異常情況的解決方案進(jìn)行處理以后,餐廳照舊營(yíng)業(yè),顧客接著用餐(程序照舊能夠正常運(yùn)行)。
但還有1種情況,可能不管我們?cè)鯓訕佑押玫膰L試進(jìn)行解決,都難以讓顧客滿(mǎn)意。這類(lèi)顧客就是傳說(shuō)中被稱(chēng)為“砸場(chǎng)子”的顧客。
針對(duì)這類(lèi)情況,我們可能就要采取更加“強(qiáng)硬的措施”了。例如直接報(bào)警把他帶走(不讓程序繼續(xù)運(yùn)行了),這就是所謂的運(yùn)行時(shí)異常:
可以看到出現(xiàn)該運(yùn)行時(shí)異常,程序?qū)⒅苯颖唤K止運(yùn)行,砸場(chǎng)子的人直接被警察帶走了。
那末接下來(lái),我們就能夠來(lái)看看第2種使用情況了,甚么是所謂的“引發(fā)連鎖效應(yīng)的毛病”。
舉個(gè)例子來(lái)講,以我們上面用到的“被除數(shù)為0”的異常情況。你可能會(huì)思考:傳入的被除數(shù)為0,這樣的情況我們是可以斟酌到的。
并且我們也能夠針對(duì)這樣的毛病給出對(duì)應(yīng)的措施。那Java為何不將這樣的異常定義為編譯時(shí)檢測(cè)異常呢?
那末我無(wú)妨假定ArithmeticException就是編譯時(shí)檢測(cè)異常,所以我們必須對(duì)其作出處理,那末可能出現(xiàn)這樣的代碼:
如果傳入的被除數(shù)為0,就返回負(fù)數(shù)“⑴”,“⑴”就代表這個(gè)運(yùn)算出錯(cuò)了。
因而這時(shí)候有1個(gè)調(diào)用者,恰好調(diào)用了我們的除法運(yùn)算方法計(jì)算“5除以0的結(jié)果”,天經(jīng)地義的,他得到的結(jié)果為:
“5除以0的結(jié)果為⑴”。好了,這下叼了,這哥們立馬拿著這個(gè)運(yùn)算結(jié)果,去向他的朋友夸耀:
你們都是2B吧,算不出5除以0等于多少是吧?告知你們,等于⑴。因而,在朋友的眼中,他成2B了。