拋出了無數的Exception,但是Exception到底是啥?解開Exception的神祕面紗...

java異常



什麼是異常呢?

定義:當一個程序在運行過程中,出現了一些非正常執行流程的指令,那麼就會產生一個事件對象,這個事件對象在java就簡稱爲異常(Exception)。
An exception is an event, which occurs during the execution of a program, that disrupts the normal flow of the program's instructions.


異常Handler:

當一個異常出現了,那麼jvm就會去尋找一個Handler來處理這個異常。
下面樹jvm調用過程以及Handler的調用過程,從圖中可以看出,Handler過程是反序的。
 





異常的分類:

異常主要分爲三類:
Error,代表這個異常非常致命,不需要被異常Handler捕獲。
Checked Exception:受檢異常,代表異常屬於符合用戶預期的異常,通常情況下,用戶會採取相應的措施,嘗試從異常中恢復,需要被異常Handler捕獲。
Runtime Exception:運行時異常,通常是一個bug,一般用於表示該異常是用戶沒有預料到的,不需要被異常Handler捕獲。



語法結構:

try語法結構:
try {
    code
}
catch and finally blocks . . .


catch語法結構:
try {


} catch (ExceptionType name) {


} catch (ExceptionType name) {


}


finally語法結構:
try{


}catch(ExceptionType name){


}finally {





直接在方法中拋出並且捕獲異常的結構:
public void method1() throws ExceptionType{


}


直接在方法中拋出異常
public void method1(){
    if(index < 0){
        throw new EmptyStackException();
    }
}

異常的作用:

先看一個方法,使用的是僞代碼...
readFile {
    open the file;
    determine its size;
    allocate that much memory;
    read the file into memory;
    close the file;
}

在這個方法中,我們會執行如下幾個步驟
1打開文件
2查看文件大小
3開闢內存空間
4讀取文件內容到內存中
5關閉文件

但是如果這個方法出現如下問題
1文件不可打開
2文件大小不可預知
3內存空間不夠了
4如果讀取文件失敗了
5關閉文件失敗了

如果不適用異常來處理這些異常,那麼代碼會是怎樣子呢?
errorCodeType readFile {
    initialize errorCode = 0;
    
    open the file;
    if (theFileIsOpen) {
        determine the length of the file;
        if (gotTheFileLength) {
            allocate that much memory;
            if (gotEnoughMemory) {
                read the file into memory;
                if (readFailed) {
                    errorCode = -1;
                }
            } else {
                errorCode = -2;
            }
        } else {
            errorCode = -3;
        }
        close the file;
        if (theFileDidntClose && errorCode == 0) {
            errorCode = -4;
        } else {
            errorCode = errorCode and -4;
        }
    } else {
        errorCode = -5;
    }
    return errorCode;
}
每次客戶端調用這個方法,都需要判斷返回的狀態碼,從而來決定下一步來做什麼...比如:
int status = readFile();
switch(status){
case -1: do something...
case -2: do someting...
}


這種代碼是會讓你發瘋的,特別是一段時間之後,你再回來看這些代碼,你會發現可讀性特別差,如果在readFile()中再添加一些新的狀態碼,那麼客戶端的代碼需要跟着一起
改變,這會給維護帶來毀滅性的的工作,並且很容易引入新的bug。


下面是使用異常之後的代碼:

readFile {
    try {
        open the file;
        determine its size;
        allocate that much memory;
        read the file into memory;
        close the file;
    } catch (fileOpenFailed) {
       doSomething;
    } catch (sizeDeterminationFailed) {
        doSomething;
    } catch (memoryAllocationFailed) {
        doSomething;
    } catch (readFailed) {
        doSomething;
    } catch (fileCloseFailed) {
        doSomething;
    }
}

通過異常,我們可以讓代碼的可讀性更加完整,並且如果新增加異常,也不會改動客戶端的代碼。


我們再來看看另外一個列子

method1 {
    call method2;
}

method2 {
    call method3;
}

method3 {
    call readFile;
}
這是一個方法調用棧,方法method1() call method2() call method()3 call readFile()

如果沒用異常處理機制,那麼每個方法都會被強制進行錯誤診斷,那可是相當麻煩的,如下代碼:

method1 {
    errorCodeType error;
    error = call method2;
    if (error)
        doErrorProcessing;
    else
        proceed;
}

errorCodeType method2 {
    errorCodeType error;
    error = call method3;
    if (error)
        return error;
    else
        proceed;
}

errorCodeType method3 {
    errorCodeType error;
    error = call readFile;
    if (error)
        return error;
    else
        proceed;
}

好吧,看完之後,你會被很多無關代碼弄得心煩意亂了,各種亂...

下面我們使用異常來處理:

method1 {
    try {
        call method2;
    } catch (exception e) {
        doErrorProcessing;
    }
}

method2 throws exception {
    call method3;
}

method3 throws exception {
    call readFile;
}

我們僅僅在只關心的方法中處理異常...瞬間世界美好了。


另外,異常可以幫助我們將一些錯誤進行分類處理。比如在java.io中,需要各種各種的IO錯誤,比如打開文件失敗,訪問文件失敗,關閉輸出流失敗等,我們都可以將這些異常歸結爲IOException,從更加高的層次去看待問題。


自定義異常:

通常情況下,如果你在開發一個工具包,出現了異常,那麼用戶可以直接自定義異常。異常的自定義非常簡單,通常類名就表示錯誤類別。如:IllegalArgumentException,IOException,NegativeIndexException,FileNotFoundException等...但是有一點需要注意的是,選擇繼承的異常類很重要。如果錯誤是不可預知的,屬於bug的,可以繼承RuntionException,要麼繼承Exception吧。



總結:異常給了程序更加高的靈活性,並且讓代碼更加通熟易懂。所以正確使用異常,是非常重要的。在捕獲異常時,應該是做到具體的異常類型,很多人懶惰,就直接在每個方法中throws Exception...可以這會給調試bug帶來極大的困難。比如說異常是在一個10個方法堆棧中拋出的,那麼異常鏈就會非常的大,想要定位到錯誤的所在地是困難的。同樣,也不能隨意的捕獲異常甚至是將異常丟掉了,如下代碼try{}catch(Exception){ignore...}。程序明明出現了錯誤,但是控制檯啥也沒有輸出,這絕對是一個大坑。所以捕獲異常的情況一般是:能處理異常,那麼就可以捕獲異常,如果不行,那麼就拋出異常,丟給調用者來決定異常的處理情況。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章