Exception這個(gè)詞表達(dá)的是一種“例外”情況,亦即正常情況之外的一種“異乘纹郏”。在問
題發(fā)生的時(shí)候胰伍,我們可能不知具體該如何解決迄靠,但肯定知道已不能不顧一切地繼續(xù)下去。此時(shí)喇辽,必須堅(jiān)決地停下來,并由某人雨席、某地指出發(fā)生了什么事情菩咨,以及該采取何種對策。但為了真正解決問題陡厘,當(dāng)?shù)乜赡懿]有足夠多的信息抽米。因此,我們需要將其移交給更級(jí)的負(fù)責(zé)人糙置,令其作出正確的決定(類似一個(gè)命令鏈)云茸。
異常機(jī)制的另一項(xiàng)好處就是能夠簡化錯(cuò)誤控制代碼。我們再也不用檢查一個(gè)特定的錯(cuò)誤谤饭,然后在程序的多處地方對其進(jìn)行控制标捺。此外,也不需要在方法調(diào)用的時(shí)候檢查錯(cuò)誤(因?yàn)楸WC有人能捕獲這里的錯(cuò)誤)揉抵。我們只需要在一個(gè)地方處理問題:“異惩鋈荩控制模塊”或者“異常控制器”冤今。這樣可有效減少代碼量闺兢,并將那些用于描述具體操作的代碼與專門糾正錯(cuò)誤的代碼分隔開。一般情況下戏罢,用于讀取屋谭、寫入以及調(diào)試的代碼會(huì)變得更富有條理脚囊。
1.基本異常
“異常條件”表示出現(xiàn)應(yīng)中止方法或作用域繼續(xù)的問題。將異常條件與普通問題區(qū)分開是非常重要的桐磁。在普通問題的情況下悔耘,我們在當(dāng)?shù)匾褤碛凶銐虻男畔ⅲ稍谀撤N程度上解決碰到的問題所意。而在異常條件的情況下淮逊,卻無法繼續(xù)下去,因?yàn)楫?dāng)?shù)貨]有提供解決問題所需的足夠多的信息扶踊。
產(chǎn)生一個(gè)異常時(shí)泄鹏,會(huì)發(fā)生幾件事情。首先秧耗,按照與創(chuàng)建Java對象一樣的方法創(chuàng)建異常對象:在內(nèi)存“堆”里备籽,使用new來創(chuàng)建。隨后分井,停止當(dāng)前執(zhí)行路徑(記住不可沿這條路徑繼續(xù)下去)车猬,然后從當(dāng)前的環(huán)境中拋出異常對象。此時(shí)尺锚,異持槿颍控制機(jī)制會(huì)接管一切,并開始查找一個(gè)恰當(dāng)?shù)牡胤教北纾糜诶^續(xù)程序的執(zhí)行伏嗜。這個(gè)恰當(dāng)?shù)牡胤奖闶恰爱惓L幚砥鳌保╡xception handler),它的職責(zé)是從問題中恢復(fù)伐厌,使程序要么嘗試另一條執(zhí)行路徑承绸,要么簡單地繼續(xù)。
1.1 異常自變量
在所有標(biāo)準(zhǔn)異常中挣轨,存在著兩個(gè)構(gòu)建器:第一個(gè)是默認(rèn)構(gòu)建器军熏,第二個(gè)則需使用一個(gè)字串自變量,使我們能在異常里置入相關(guān)信息卷扮。
可根據(jù)需要擲出任何類型的“可擲”對象荡澎。典型情況下,我們要為每種不同類型的錯(cuò)誤“擲”出一類不同的異常晤锹。我們的思路是在異常對象以及挑選的異常對象類型中保存信息衔瓮,所以在更大場景中的某個(gè)人可知道如何對待我們的異常(通常,唯一的信息是異常對象的類型抖甘,而異常對象中保存的沒什么意義)热鞍。
2.異常的捕獲
若某個(gè)方法產(chǎn)生一個(gè)異常,必須保證該異常能被捕獲,并獲得正確對待薇宠。對于Java的異常處理機(jī)制偷办,它的一個(gè)好處就是允許我們在一個(gè)地方將精力集中在要解決的問題上,然后在另一個(gè)地方對待來自那個(gè)代碼內(nèi)部的錯(cuò)誤澄港。
2.1 try塊
若不想throw后直接離開方法椒涯,可在那個(gè)方法內(nèi)部設(shè)置一個(gè)特殊的代碼塊,用它捕獲異常回梧。這就叫作“try塊”废岂。
2.2 異常處理器
當(dāng)然,生成的異常必須在某個(gè)地方中止狱意。這個(gè)“地方”便是異常處理器或者異常處理模塊湖苞。而且針對想捕獲的每種異常類型,都必須有一個(gè)相應(yīng)的異常處理器详囤。異常處理器緊接在try塊后面财骨,且用catch(捕獲)關(guān)鍵字標(biāo)記。
處理器必須“緊接”在try塊后面藏姐。若“擲”出一個(gè)異常例隆箩,異常處理機(jī)制就會(huì)搜尋自變量與異常類型相符的第一個(gè)處理器。隨后羔杨,它會(huì)進(jìn)入那個(gè)catch從句捌臊,并認(rèn)為異常已得到處理(一旦catch從句結(jié)束,對處理器的搜索也會(huì)停止)兜材。
2.2.1 中斷與恢復(fù)
在異常處理理論中理澎,共存在兩種基本方法。
- 1)中斷
假定錯(cuò)誤非常關(guān)鍵护姆,沒有辦法返回異常發(fā)生的地方。無論誰只要“擲”出一個(gè)異常掏击,就表明沒有辦法補(bǔ)救錯(cuò)誤卵皂,而且也不希望再回來。
- 2)恢復(fù)
意味著異常處理器有責(zé)任來糾正當(dāng)前的狀況砚亭,然后取得出錯(cuò)的方法灯变,假定下一次會(huì)成功執(zhí)行。若使用恢復(fù)捅膘,意味著在異常得到處理以后仍然想繼續(xù)執(zhí)行添祸。
2.3 異常規(guī)范
異常規(guī)范采用了一個(gè)額外的關(guān)鍵字:throws;后面跟隨全部潛在的異常類型寻仗。
2.4 捕獲所有異常
可創(chuàng)建一個(gè)處理器刃泌,令其捕獲所有類型的異常。具體的做法是捕獲基礎(chǔ)類異常類型Exception。
這段代碼能捕獲任何異常耙替,所以在實(shí)際使用時(shí)最好將其置于處理器列表的末尾亚侠,防止跟隨在后面的任何特殊異常處理器失效。
由于Exception類是它們的基礎(chǔ)俗扇,所以我們不會(huì)獲得關(guān)于異常太多的信息硝烂,但可調(diào)用來自它的基礎(chǔ)類Throwable的方法。
2.5 重新拋出異常
在某些情況下铜幽,我們想重新擲出剛才產(chǎn)生過的異常滞谢,特別是在用Exception捕獲所有可能的異常時(shí)。由于我們已擁有當(dāng)前異常的句柄除抛,所以只需簡單地重新擲出那個(gè)句柄即可狮杨。
若只是簡單地重新擲出當(dāng)前異常,我們打印出來的镶殷、與printStackTrace()內(nèi)的那個(gè)異常有關(guān)的信息會(huì)與異常的起源地對應(yīng)禾酱,而不是與重新擲出它的地點(diǎn)對應(yīng)。若想放置新的堆棧跟蹤信息绘趋,可調(diào)用fillInStackTrace()颤陶,它會(huì)返回一個(gè)特殊的異常對象:將當(dāng)前堆棧的信息填充到原來的異常對象里。
3.標(biāo)準(zhǔn)Java異常
Java包含了一個(gè)名為Throwable的類陷遮,它對可以作為異匙易撸“擲”出的所有東西進(jìn)行了描述。Throwable對象有兩種常規(guī)類型(亦即“從Throwable繼承”)帽馋。其中搅方,Error代表編譯期和系統(tǒng)錯(cuò)誤,我們一般不必特意捕獲它們(除在特殊情況以外)绽族。Exception是可以從任何標(biāo)準(zhǔn)Java庫的類方法中“擲”出的基本類型姨涡。此外,它們亦可從我們自己的方法以及運(yùn)行期偶發(fā)事件中“擲”出吧慢。
3.1 特例:RuntimeException
看起來似乎在傳遞進(jìn)入一個(gè)方法的每個(gè)句柄中都必須檢查null(因?yàn)椴恢勒{(diào)用者是否已傳遞了一個(gè)有效的句柄)涛漂,這無疑是相當(dāng)可怕的。但幸運(yùn)的是检诗,我們根本不必這樣做——它屬于Java進(jìn)行的標(biāo)準(zhǔn)運(yùn)行期檢查的一部分匈仗。若對一個(gè)空句柄發(fā)出了調(diào)用,Java會(huì)自動(dòng)產(chǎn)生一個(gè)NullPointerException異常逢慌。
由于它們用于指出編程中的錯(cuò)誤悠轩,所以幾乎永遠(yuǎn)不必專門捕獲一個(gè)“運(yùn)行期異常”——RuntimeException——它在默認(rèn)情況下會(huì)自動(dòng)得到處理攻泼。
如果不捕獲這些異常火架,又會(huì)出現(xiàn)什么情況呢鉴象?由于編譯器并不強(qiáng)制異常規(guī)范捕獲它們,所以假如不捕獲的話距潘,一個(gè)RuntimeException可能滲透到main()方法炼列。
假若一個(gè)RuntimeException在沒有捕獲異常的情況下直達(dá)main()垮兑,那么當(dāng)程序退出時(shí)皇忿,會(huì)為那個(gè)異常調(diào)用printStackTrace()陵刹。
請務(wù)必記妆炯ァ:只能在代碼中忽賂RuntimeException(及其子類)類型的異常吞瞪,其他類型異常的處理都是由編譯器強(qiáng)制實(shí)施的骡楼。究其原因淡喜,RuntimeException代表的是編程錯(cuò)誤:
- 1)無法預(yù)料的錯(cuò)誤陨帆。比如從你控制范圍之外傳遞進(jìn)來的null引用骚亿。
- 2)作為程序員已亥,應(yīng)該在代碼中進(jìn)行檢查的錯(cuò)誤。(比如對于ArraylndexOutOf-BoundsException, 就得注意一下數(shù)組的大小了来屠。)在一個(gè)地方發(fā)生的異常虑椎,常常會(huì)在另一個(gè)地方導(dǎo)致錯(cuò)誤。
4.使用finally進(jìn)行清理
對千一些代碼俱笛,可能會(huì)希望無論t盯塊中的異常是否拋出捆姜,它們都能得到執(zhí)行。這通常適用 千內(nèi)存回收之外的情況(因?yàn)榛厥沼衫厥掌魍瓿桑┯ぁ榱诉_(dá)到這個(gè)效果泥技,可以在異常處理程序后面加上finally子句。
4.1 何時(shí)使用finally
當(dāng)要把除內(nèi)存之外的資源恢復(fù)到它們的初始狀態(tài)時(shí)磕仅,就要用到finally子句珊豹。這種需要清理的 資源包括:已經(jīng)打開的文件或網(wǎng)絡(luò)連接,在屏幕上畫的圖形榕订,甚至可以是外部世界的某個(gè)開關(guān)店茶。
4.2 在return中使用finally
4.3 缺憾:異常丟失
異常作為程序出錯(cuò)的標(biāo)志,決不應(yīng)該被忽略劫恒,但它還是有可能被輕易地忽略贩幻。用某些特殊的方式使用finally子句,就會(huì)發(fā)生這種情況兼贸。
5.異常的限制
當(dāng)覆蓋方法的時(shí)候段直, 只能拋出在基類方法的異常說明里列出的那些異常吃溅。這個(gè)限制很有用溶诞, 因?yàn)檫@意味著, 當(dāng)基類使用的代碼應(yīng)用到其派生類對象的時(shí)候决侈,一樣能夠工作(當(dāng)然螺垢, 這是面向?qū)ο蟮幕靖拍睿?異常也不例外喧务。
異常限制對構(gòu)造器不起作用。然而枉圃,因?yàn)榛悩?gòu)造器必須以這樣或那樣的方式被調(diào)用(這里1ill1 默認(rèn)構(gòu)造器將自動(dòng)被調(diào)用)功茴,派生類構(gòu)造器的異常說明必須包含基類構(gòu)造器的異常說明。
派生類構(gòu)造器不能捕獲基類構(gòu)造器拋出的異常孽亲。
如果處理的剛好是StormyInning對象的話坎穿,編譯器只會(huì)強(qiáng)制要求你捕獲這個(gè)類所拋出的異常。但是如果將它向上轉(zhuǎn)型成基類型返劲,那么編譯器就會(huì)(正確地)要求你捕獲基類的異常玲昧。所有這些限制都是為了能產(chǎn)生更為強(qiáng)壯的異 常處理代碼。
盡管在繼承過程中篮绿,編譯器會(huì)對異常說明做強(qiáng)制要求孵延,但異常說明本身并不屬于方法類型的一部分,方法類型是由方法的名字與參數(shù)的類型組成的亲配。因此尘应,不能基千異常說明來重載方法。
此外吼虎,一個(gè)出現(xiàn)在基類方法的異常說明中的異常犬钢,不一定會(huì)出現(xiàn)在派生類方法的異常說明匝習(xí) 里。這點(diǎn)同繼承的規(guī)則明顯不同鲸睛,在繼承中娜饵,基類的方法必須出現(xiàn)在派生類里,換句話說官辈,在繼承和覆蓋的過程中箱舞,某個(gè)特定方法的“異常說明的接口”不是變大了而是變小了一這恰好和類接口在繼承時(shí)的情形相反。
6.構(gòu)造器
如果異常發(fā)生了拳亿,所有東西能被正確清理嗎晴股?
盡管大多數(shù)情況下是非常安全的,但涉及構(gòu)造器時(shí)肺魁,問題就出現(xiàn)了电湘。構(gòu)造器會(huì)把對象設(shè)置成安全的初始狀態(tài),但還會(huì)有別的動(dòng)作鹅经,比如打開一個(gè)文件寂呛,這樣的動(dòng)作只有在對象使用完畢并且用戶調(diào)用了特殊的清理方法之后才能得以清理。如果在構(gòu)造器內(nèi)拋出了異常瘾晃,這些清理行為也許就不能正常工作了贷痪。
對于在構(gòu)造階段可能會(huì)拋出異常,井且要求清理的類蹦误,最安全的使用方式是使用嵌套的try 子句劫拢。
這種通用的清理慣用法在構(gòu)造器不拋出任何異常時(shí)也應(yīng)該運(yùn)用肉津,其基本規(guī)則是:在創(chuàng)建需要清理的對象之后,立即進(jìn)入一個(gè)try-finally語句塊舱沧。
7.異常匹配
拋出異常的時(shí)候妹沙,異常處理系統(tǒng)會(huì)按照代碼的書寫順序找出 “最近” 的處理程序。 找到匹配的處理程序之后熟吏,它就認(rèn)為異常將得到處理距糖,然后就不再繼續(xù)查找。
查找的時(shí)候井不要求拋出的異常同處理程序所聲明的異常完全匹配牵寺。 派生類的對象也可以匹配其基類的處理程序肾筐。
如果決定在方法里加上更多派生異常的話,只要客戶程序員捕獲的是基類異常缸剪,那么它們的代碼就無需更改吗铐。
8.其他可選方式
異常代表了當(dāng)前方法不能繼續(xù)執(zhí)行的情形。 開發(fā)異常處理系統(tǒng)的原因是杏节,如果為每個(gè)方法所有可能發(fā)生的錯(cuò)誤都進(jìn)行處理的話唬渗, 任務(wù)就顯得過于繁重了,程序員也不愿意這么做奋渔。結(jié)果常常是將錯(cuò)誤忽略镊逝。 應(yīng)該注意到,開發(fā)異常處理的初衷是為了方便程序員處理錯(cuò)誤嫉鲸。
異常處理的一個(gè)重要原則是 “只有在你知道如何處理的情況下才捕獲異吵潘猓”。實(shí)際上玄渗, 異常處理的一個(gè)重要目標(biāo)就是把錯(cuò)誤處理的代碼同錯(cuò)誤發(fā)生的地點(diǎn)相分離座菠。 這使你能在一段代碼中專注于要完成的事情,至于如何處理錯(cuò)誤藤树,則放在另一段代碼中完成浴滴。 這樣以來,主干代碼就不會(huì)與錯(cuò)誤處理邏輯混在一起岁钓,也更容易理解和維護(hù)升略。 通過允許一個(gè)處理程序去處理多個(gè)出錯(cuò)點(diǎn), 異常處理還使得錯(cuò)誤處理代碼的數(shù)鹽趨向于減少屡限。
”被檢查的異称废“ 使這個(gè)問題變得有些復(fù)雜,因?yàn)樗鼈儚?qiáng)制你在可能還沒準(zhǔn)備好處理錯(cuò)誤的時(shí)候被迫加上catch子句钧大,這就導(dǎo)致了吞食則有害 (harmful if swallowed) 的問題翰撑。異常確實(shí)發(fā)生了,但 “吞食” 后它卻完全消失了拓型。
只有當(dāng)Java修改了它那類似C++的模型额嘿,使異常成為報(bào)告錯(cuò)誤的唯一方式, 那時(shí) “被檢查的異沉哟欤” 的額外限制也許就會(huì)變得沒有那么必要了册养。
1)不在于編譯器是否會(huì)強(qiáng)制程序員去處理錯(cuò)誤,而是要有一致的压固、 使用異常來報(bào)告錯(cuò)誤的模型球拦。
2)不在千什么時(shí)候進(jìn)行檢查, 而是一定要有類型檢查帐我。也就是說坎炼, 必須強(qiáng)制程序使用正確的類型, 至干這種強(qiáng)制施加于編譯時(shí)還是運(yùn)行時(shí)拦键,那倒沒關(guān)系谣光。
此外,減少編譯時(shí)施加的約束能顯著提高程序員的編程效率芬为。事實(shí)上萄金, 反射和泛型就是用來補(bǔ)償靜態(tài)類型檢查所帶來的過多限制。
果發(fā)現(xiàn)有些 ”被檢查的異趁碾” 擋住了路氧敢, 尤其是發(fā)現(xiàn)你不得不去對付那些不知道該如何處理的異常,還是有些辦法的询张。
- 1)把異常傳遞給控制臺(tái)
- 2)把 “被檢查的異乘锕裕“ 轉(zhuǎn)換為 “不檢查的異常”:以直接把“被檢查的異撤菅酰“包裝進(jìn)RuntiJneException里面
9.異常使用指南
應(yīng)該在下列情況下使用異常:
- 1)在恰當(dāng)?shù)募?jí)別處理問題唯袄。(在知道該如何處理的情況下才捕獲異常。)
- 2)解決問題并且重新調(diào)用產(chǎn)生異常的方法蜗帜。
- 3)進(jìn)行少許修補(bǔ)越妈, 然后繞過異常發(fā)生的地方繼續(xù)執(zhí)行。
- 4)用別的數(shù)據(jù)進(jìn)行計(jì)算钮糖, 以代替方法預(yù)計(jì)會(huì)返回的值梅掠。
- 5)把當(dāng)前運(yùn)行環(huán)境下能做的事情盡量做完, 然后把相同的異常重拋到更高層店归。
- 6)把當(dāng)前運(yùn)行環(huán)境下能做的事情盡量做完阎抒, 然后把不同的異常拋到更高層。
- 7)終止程序消痛。
- 8)進(jìn)行簡化且叁。(如果你的異常模式使問題變得太復(fù)雜, 那用起來會(huì)非常痛苦也很煩人秩伞。)
- 9)讓類庫和程序更安全逞带。(這既是在為調(diào)試做短期投資欺矫, 也是在為程序的健壯性做長期投資。
10.總結(jié)
異常處理的優(yōu)點(diǎn)之一就是它使得你可以在某處集中檔力處理你要解決的問題展氓, 而在另一處處理你編寫的這段代碼中產(chǎn)生的錯(cuò)誤穆趴。
盡管異常通常被認(rèn)為是一種工具, 使得你可以在運(yùn)行時(shí)報(bào)告錯(cuò)誤并從錯(cuò)誤中恢復(fù)遇汞, 但是我一直懷疑到底有多少時(shí)候 “恢復(fù)“ 真正得以實(shí)現(xiàn)了未妹, 或者能夠得以實(shí)現(xiàn)。 我認(rèn)為這種情況少千10%, 并且即便是這10%, 也只是將棧展開到某個(gè)已知的穩(wěn)定狀態(tài)空入, 而井沒有實(shí)際執(zhí)行任何種類的恢復(fù)性行為络它。
無論這是否正確, 我一直相信 “報(bào)告” 功能是異常的精髓所在歪赢。Java堅(jiān)定地強(qiáng)調(diào)將所有的錯(cuò)誤都以異常形式報(bào)告的這一事實(shí)化戳, 正是它遠(yuǎn)遠(yuǎn)超過諸如C++這類語言的長處之一,因?yàn)樵贑++這類語言中埋凯, 需要以大量不同的方式來報(bào)告錯(cuò)誤迂烁, 或者根本就沒有提供錯(cuò)誤報(bào)告功能。 一致的錯(cuò)誤報(bào)告系統(tǒng)意味著递鹉, 你再也不必對所寫的每段代碼盟步,都質(zhì)問自己“錯(cuò)誤是否正在成為漏網(wǎng)之魚?”(只要你沒有“吞咽“異常躏结,這是關(guān)鍵所在H磁獭)