1. 異常機制 異常機制是指當程序出現錯誤后,程序如何處理。具體來(lái)說(shuō),異常機制提供了程序退出的安全通道。當出現錯誤后,程序執行的流程發(fā)生改變,程序的控制權轉移到異常處理器。
傳統的處理異常的辦法是,函數返回一個(gè)特殊的結果來(lái)表示出現異常(通常這個(gè)特殊結果是大家約定俗稱(chēng)的),調用該函數的程序負責檢查并分析函數返回的結果。這樣做有如下的弊端:例如函數返回-1代表出現異常,但是如果函數確實(shí)要返回-1這個(gè)正確的值時(shí)就會(huì )出現混淆;可讀性降低,將程序代碼與處理異常的代碼混爹在一起;由調用函數的程序來(lái)分析錯誤,這就要求客戶(hù)程序員對庫函數有很深的了解。
異常處理的流程:
① 遇到錯誤,方法立即結束,并不返回一個(gè)值;同時(shí),拋出一個(gè)異常對象 。
② 調用該方法的程序也不會(huì )繼續執行下去,而是搜索一個(gè)可以處理該異常的異常處理器,并執行其中的代碼 。
2 異常的分類(lèi)
異常的分類(lèi):
① 異常的繼承結構:基類(lèi)為T(mén)hrowable,Error和Exception繼承Throwable,RuntimeException和IOException等繼承Exception,具體的RuntimeException繼承RuntimeException。
② Error和RuntimeException及其子類(lèi)成為未檢查異常(unchecked),其它異常成為已檢查異常(checked)。
每個(gè)類(lèi)型的異常的特點(diǎn) Error體系 : Error類(lèi)體系描述了Java運行系統中的內部錯誤以及資源耗盡的情形。應用程序不應該拋出這種類(lèi)型的對象(一般是由虛擬機拋出)。如果出現這種錯誤,除了盡力使程序安全退出外,在其他方面是無(wú)能為力的。所以,在進(jìn)行程序設計時(shí),應該更關(guān)注Exception體系。
Exception體系包括RuntimeException體系和其他非RuntimeException的體系 :① RuntimeException:RuntimeException體系包括錯誤的類(lèi)型轉換、數組越界訪(fǎng)問(wèn)和試圖訪(fǎng)問(wèn)空指針等等。處理RuntimeException的原則是:如果出現RuntimeException,那么一定是程序員的錯誤。例如,可以通過(guò)檢查數組下標和數組邊界來(lái)避免數組越界訪(fǎng)問(wèn)異常。
②其他非RuntimeException(IOException等等):這類(lèi)異常一般是外部錯誤,例如試圖從文件尾后讀取數據等,這并不是程序本身的錯誤,而是在應用環(huán)境中出現的外部錯誤。
與C++異常分類(lèi)的不同 :
① Java中RuntimeException這個(gè)類(lèi)名起的并不恰當,因為任何異常都是運行時(shí)出現的。(在編譯時(shí)出現的錯誤并不是異常,換句話(huà)說(shuō),異常就是為了解決程序運行時(shí)出現的的錯誤)。
② C++中logic_error與Java中的RuntimeException是等價(jià)的,而runtime_error與Java中非RuntimeException類(lèi)型的異常是等價(jià)的。
3 異常的使用方法 聲明方法拋出異常 ① 語(yǔ)法:throws(略)
② 為什么要聲明方法拋出異常?
方法是否拋出異常與方法返回值的類(lèi)型一樣重要。假設方法拋出異常確沒(méi)有聲明該方法將拋出異常,那么客戶(hù)程序員可以調用這個(gè)方法而且不用編寫(xiě)處理異常的代碼。那么,一旦出現異常,那么這個(gè)異常就沒(méi)有合適的異??刂破鱽?lái)解決。
③ 為什么拋出的異常一定是已檢查異常?
RuntimeException與Error可以在任何代碼中產(chǎn)生,它們不需要由程序員顯示的拋出,一旦出現錯誤,那么相應的異常會(huì )被自動(dòng)拋出。而已檢查異常是由程序員拋出的,這分為兩種情況:客戶(hù)程序員調用會(huì )拋出異常的庫函數(庫函數的異常由庫程序員拋出);客戶(hù)程序員自己使用throw語(yǔ)句拋出異常。遇到Error,程序員一般是無(wú)能為力的;遇到RuntimeException,那么一定是程序存在邏輯錯誤,要對程序進(jìn)行修改(相當于調試的一種方法);只有已檢查異常才是程序員所關(guān)心的,程序應該且僅應該拋出或處理已檢查異常。
注意:覆蓋父類(lèi)某方法的子類(lèi)方法不能拋出比父類(lèi)方法更多的異常,所以,有時(shí)設計父類(lèi)的方法時(shí)會(huì )聲明拋出異常,但實(shí)際的實(shí)現方法的代碼卻并不拋出異常,這樣做的目的就是為了方便子類(lèi)方法覆蓋父類(lèi)方法時(shí)可以?huà)伋霎惓!?/div>
如何拋出異常
① 語(yǔ)法:throw(略)
② 拋出什么異常?對于一個(gè)異常對象,真正有用的信息時(shí)異常的對象類(lèi)型,而異常對象本身毫無(wú)意義。比如一個(gè)異常對象的類(lèi)型是ClassCastException,那么這個(gè)類(lèi)名就是唯一有用的信息。所以,在選擇拋出什么異常時(shí),最關(guān)鍵的就是選擇異常的類(lèi)名能夠明確說(shuō)明異常情況的類(lèi)。
③ 異常對象通常有兩種構造函數:一種是無(wú)參數的構造函數;另一種是帶一個(gè)字符串的構造函數,這個(gè)字符串將作為這個(gè)異常對象除了類(lèi)型名以外的額外說(shuō)明。
④ 創(chuàng )建自己的異常:當Java內置的異常都不能明確的說(shuō)明異常情況的時(shí)候,需要創(chuàng )建自己的異常。需要注意的是,唯一有用的就是類(lèi)型名這個(gè)信息,所以不要在異常類(lèi)的設計上花費精力。
捕獲異常
如果一個(gè)異常沒(méi)有被處理,那么,對于一個(gè)非圖形界面的程序而言,該程序會(huì )被中止并輸出異常信息;對于一個(gè)圖形界面程序,也會(huì )輸出異常的信息,但是程序并不中止,而是返回用錯誤頁(yè)面。
語(yǔ)法:try、catch和finally(略),控制器模塊必須緊接在try塊后面。若擲出一個(gè)異常,異??刂茩C制會(huì )搜尋參數與異常類(lèi)型相符的第一個(gè)控制器隨后它會(huì )進(jìn)入那個(gè)catch 從句,并認為異常已得到控制。一旦catch 從句結束對控制器的搜索也會(huì )停止。
捕獲多個(gè)異常(注意語(yǔ)法與捕獲的順序)(略)
finally的用法與異常處理流程(略)
異常處理做什么?對于Java來(lái)說(shuō),由于有了垃圾收集,所以異常處理并不需要回收內存。但是依然有一些資源需要程序員來(lái)收集,比如文件、網(wǎng)絡(luò )連接和圖片等資源。
應該聲明方法拋出異常還是在方法中捕獲異常?原則:捕捉并處理哪些知道如何處理的異常,而傳遞哪些不知道如何處理的異常。
再次拋出異常
①為什么要再次拋出異常? 在本級中,只能處理一部分內容,有些處理需要在更高一級的環(huán)境中完成,所以應該再次拋出異常。這樣可以使每級的異常處理器處理它能夠處理的異常。
②異常處理流程 :對應與同一try塊的catch塊將被忽略,拋出的異常將進(jìn)入更高的一級。
4 關(guān)于異常的其他問(wèn)題
① 過(guò)度使用異常 :首先,使用異常很方便,所以程序員一般不再愿意編寫(xiě)處理錯誤的代碼,而僅僅是簡(jiǎn)簡(jiǎn)單單的拋出一個(gè)異常。這樣做是不對的,對于完全已知的錯誤,應該編寫(xiě)處理這種錯誤的代碼,增加程序的魯棒性。另外,異常機制的效率很差。
② 將異常與普通錯誤區分開(kāi):對于普通的完全一致的錯誤,應該編寫(xiě)處理這種錯誤的代碼,增加程序的魯棒性。只有外部的不能確定和預知的運行時(shí)錯誤才需要使用異常。
③ 異常對象中包含的信息 :一般情況下,異常對象唯一有用的信息就是類(lèi)型信息。但使用異常帶字符串的構造函數時(shí),這個(gè)字符串還可以作為額外的信息。調用異常對象的getMessage()、toString()或者printStackTrace()方法可以分別得到異常對象的額外信息、類(lèi)名和調用堆棧的信息。并且后一種包含的信息是前一種的超集。