異常概述
異常指的是程序在執行的過程中,出現的非正常情況,如果不處理最終會導致JVM的非正常停止。
在Java中,使用不同的類來表示不同的異常(正所謂萬物皆對象,因此異常也使用類來表示)。一旦程序出現某種異常就創建該異常類型的對象,并且拋出。然后,程序如果捕捉到這個異常對象,那么就會進行處理;如果沒有捕捉到這個異常對象,那么就會導致程序非正常停止。
因此,程序員在編寫代碼的時候,就要充分考慮到各種可能發生的異常和錯誤,極力進行避免和預防。對于實在無法避免的,要編寫相應的代碼進行異常的檢測、異常的處理,從而增強代碼的健壯性。
異常的體系結構
Throwable
java.lang.Throwable?類是Java程序執行過程中發生的異常事件對應類的根父類。
Throwable 類存在兩個子類,分別是:Error、Exception。
Error
Error 類表示的異常是:Java虛擬機無法解決的嚴重問題。如,JVM系統內部錯誤、資源耗盡等情況。一般是不會編寫針對性的代碼進行處理,畢竟編寫代碼也不好處理。
StatckOverflowError(棧內存溢出)和OutOfMemoryError(堆內存溢出,簡稱OOM)是Error較為常見的場景,一般中高級程序猿才能處理這種問題。
Exception
Exception 類表示的異常是:其他因編程錯誤或偶然的外在因素導致的一般性問題,需要編寫針對性的代碼進行處理,使程序繼續運行。否則一旦發生異常,程序就會掛掉。例如:
- 空指針異常
- 試圖讀取不存在的文件
- 網絡連接中斷
- 數組角標越界
Exception 異常又分為編譯時異常(在執行javac命令時出現的異常,因此也被稱為受檢異常)和運行時異常(在執行java命令時出現的異常,因此也被稱為非受檢異常)。
?異常的處理方式
try-catch-finally
拋抓模型
對于拋抓模型來說,分為兩個過程:一拋一抓。
對于拋過程來說,在程序執行的過程中,一旦出現異常,那就會在產生異常的代碼處生成對應異常類的對象,并將此對象拋出。一旦將對象拋出,那么就不會再繼續執行代碼,等待抓過程的實現。
對于抓過程來說,當拋出異常時,抓過程就開始了。程序針對拋過程產生的異常對象進行捕獲處理。一旦將異常處理,那么程序就會繼續執行,但是如果并沒有處理掉,那么程序就會終止。
基本結構
try {
? ? ? ? // 可能產異常的代碼
}
catch (異常類型1) {
? ? ? ? // 當產生異常類型1時的處置措施
}
catch (異常類型2) {
? ? ? ? // 當產生異常類型2時的處置措施
}
finally {
? ? ? ? // 無論是否發生異常,都無條件執行的代碼
}
?對于try結構來說,其中存放的就是可能產生異常的代碼。當異常出現時,就會生成對應異常類的對象并拋出,并且無論是否處理了異常,try結構中產生異常代碼之后的代碼都不會繼續執行。值得注意的是,try結構中的變量,出了try結構就不能使用。
對于catch結構來說,其中存放的是處理某種異常類的代碼。一個try結構可以對應無數個catch結構,畢竟代碼可能產生的異常不止一種類型。對于try拋出的異常對象來說,catch結構就會進行匹配,一旦匹配上,就會執行catch結構中的代碼,執行完畢之后,繼續向下執行。值得注意的是,如果聲明了多個catch結構,不同的異常類型不存在子父類的情況下,誰聲明在上,誰聲明在下都可以;如果多個類型滿足子父類的情況,則子類必須聲明在上面,父類必須聲明在下邊。
對于finally結構來說,其中存放的就是一定要執行的代碼,也就是說,無論能否處理了異常,finally結構中的代碼一定會執行(唯一例外的就是如果使用System.exit(0)來結束當前正在運行的Java虛擬機,那么就不會有任何的代碼進行執行)。所以,當有一定要執行的代碼時,就要把他放到finally結構中(例如在開發中,有一些資源,如輸出流、輸入流、數據庫連接以及Socket連接等,在使用完成之后,必須有顯式的關閉。否則,GC會認為這些代碼還要使用,就不會進行回收利用,進而導致內存泄漏。像這樣的就可以把關閉操作放在finallyt中)。
注意,try是必須存在的,catch和finally兩者可以同時存在,也可以存在一個,但是不能都不存在。
catch中處理異常的方式
1. 自己編寫輸出語句,表示異常的出現。
2.?printStackTrace(),打印異常的詳細信息。(推薦)
3. getMessage(),獲取發生異常的原因
開發心得
1. 對于運行時異常,通常不進行顯式的處理。一旦在程序運行過程中,出現了運行時異常,那么就根據異常的提示信息修改代碼即可。
2. 對于編譯時異常,一定要進行處理,否則編譯不通過。
throws
基本結構
public void test() throws 異常類型1, 異常類型2 ... {
? ? ? ? // 存在編譯時異常
}
?對于throws這種處理異常的方式來說,其實就是在方法聲明的地方拋出了異常,當A方法調用此方法時,A方法繼續拋出異常,B方法調用A方法時,繼續拋出異常。如果異常真正發生了,那么程序照樣會終止。因此這種方式存在的一個問題就是:是否真正處理了異常?
是否真正處理了異常?
1. 從編譯是否能通過的角度看,也看成是給出了異常萬一出現的解決方法。而所謂的解決方案就是繼續向上拋出。
2. 如果說,throws僅是將可能出現的異常拋給了此方法的調用者。調用者仍然需要考慮如何解決處理相關異常。那么從這個角度來說,就不算解決了異常。
開發中兩種方式的選擇
1. 資源一定要被執行:try-catch-finally。
2.?如果父類被重寫的方法沒有 throws 異常類型,則子類重寫的方法中如果出現異常,只能考慮使用 try-catch-finally 進行處理,不能 throws。
3.?開發中,方法a中依此調用了b、c、d等方法,方法b、c、d之間是遞進關系。此時,如果方法b、c、d中有異常,我們通常使用throws,而方法a中通常選擇使用try-catch。
手動拋出異常
在實際開發中,如果出現不滿足具體場景的代碼問題,我們就有必要手動拋出一個指定類型的對象。通俗的說,就是在實際問題的解決中,我們不想出現某些數據,那此時就可以手動拋出。
結構:throw + 異常類的對象。
所謂的手動拋出,就是拋抓模型中的拋過程。只不過手動拋出,而非程序自動拋出。
自定義異常
如何自定義異常類?
1. 繼承于現有的異常體系。
2. 通常提供幾個重載的構造器。
3. 提供一個全局變量,聲明為:static final long serialVersionUID
如何使用自定義異常類?
1. 在具體的代碼中,滿足指定條件的情況下,需要手動的使用throw + 自定義異常類對象
的方式,將異常類對象拋出。
2. 如果自定義異常類是非運行時異常,則必須考慮如何處理此異常類的對象(正是異常處理的兩種方式)。
為什么需要自定義異常類?
更希望通過異常類的名稱來判斷具體出現的問題。
舉例
/*** 自定義異常*/public class CustomException extends Exception{public CustomException() {super();}public CustomException(String message) {super(message);}public CustomException(Throwable cause) {super(cause);}}