Java 基礎(十二)異常機制

diamond_lin發表於2017-10-27

異常

一說談到異常,可能就有小夥伴說,這個啊,我 try...catch...finally用得賊溜。別急哈,後面有個 案例,看完之後你肯定會覺得自己對異常的理解也不是那麼透徹了,同時本文還可能會掃到你的一些知識盲點。額,扯遠了~

異常一般指不期而至的各種狀況,如:檔案不存在、空指標、非法引數等。
異常是一個事件,發生在程式執行期間,干擾了正常的指令流程。
Java 中使用 Throwable 類及其子類來描述各種不同的異常。因此,Java 異常都是物件,是 Throwable 的子類例項,描述了出現在一段編碼中的錯誤條件。當條件生成時,錯誤將引發異常。

Java 異常類層次結構圖:

從圖上我們可以看到,Java 異常都繼承自 Throwable,Throwable 分為兩大派系,一類是 Error(錯誤),一類是 Exception(異常)。

Error

Error 是程式設計師無法處理的錯誤,表示執行應用程式中教嚴重的問題。大多數錯誤與程式碼編寫者執行的操作無關,而表示程式碼執行時 JVM 出現的問題。比如,Java 虛擬機器執行時錯誤(VirtualMachineError),當 JVM 不再有繼續執行操作所需的記憶體資源時,將出現 OutOfMemoryError。這時異常發生時,Java 虛擬機器一般會終止執行緒。

所以這一類異常我們一般不用太糾結。

Exception

這是程式本身可以處理的異常。
Exception 有一個非常重要的子類 RuntimeException,RuntimeException 類及其子類表示 JVM 常用操作引發的錯誤,例如空值物件引用、除數為零、陣列角標越界則分別會引發 NullPointException、ArithmeticException、ArrayIndexOutOfBoundException。

敲黑板!!分清異常和錯誤的區別,異常是可以被程式本身處理的,錯誤不能。

可查異常

又稱編譯時異常,是編譯器要求必須處置的異常。
正確的程式在執行中,很容易出現的、情理可容的異常狀況。可查異常雖然是異常狀況,但在一定程度上它的發生是可以預計的,而且一旦發生這種異常狀況,就必須採取某種方式進行處理。

除了RuntimeException及其子類以外,其他的Exception類及其子類都屬於可查異常。這種異常的特點是Java編譯器會檢查它,也就是說,當程式中可能出現這類異常,要麼用try-catch語句捕獲它,要麼用throws子句宣告丟擲它,否則編譯不會通過。

不可查異常

編譯器不要求強制處置的異常,包括執行時異常(RuntimeException)和錯誤(Error)。

執行時異常

都是 RuntimeException 類及其子類。

這些異常是不檢查異常,程式中可以選擇捕獲處理,也可以不處理。這些異常一般是由程式邏輯錯誤引起的,程式應該從邏輯角度儘可能避免這類異常的發生。

編譯時異常

是RuntimeException以外的異常,型別上都屬於Exception類及其子類。從程式語法角度講是必須進行處理的異常,如果不處理,程式就不能編譯通過。

處理異常機制

遇到異常怎麼辦?要麼自己想辦法處理,要麼就拋給別人。

丟擲異常:
當一個方法出現錯誤引發異常時,方法建立異常物件並交付給執行時系統,系統物件中包含了異常型別和異常出現時的程式狀態等異常資訊。執行時系統負責尋找處置異常的程式碼並執行。

捕獲異常:
在方法丟擲異常之後,執行時系統將轉為尋找合適的異常處理器(Exception handler)。潛在的異常處理是異常發生時依次存留在呼叫棧中的方法的集合。當異常處理器所能處理的異常型別與丟擲的異常型別相符時,即為合適的異常處理器。執行時系統從發生異常的方法開始,依次回查呼叫棧中的方法,直至找到含有異常處理器的方法並執行。當執行時系統遍歷了呼叫棧都沒找到合適的異常處理器,則執行時系統終止,Java 程式終止。

對於執行時異常、錯誤和可查異常,Java 要求的異常處理方式有所不同。

  • 對於方法執行中可能出現的 Error,當執行方法不能捕捉時,Java 允許該方法不做任何丟擲宣告。因為大多數 Error 異常屬於永遠不允許發生的情況,也屬於合理的應用程式不掛捕獲的異常。
  • 對於所有可查異常,Java 規定:一個方法必須捕捉或者宣告丟擲。

能夠捕捉異常的方法,需要提供相符型別的異常處理器。也就是說,一個方法所能捕獲的異常,一點是 Java 程式碼在某處所丟擲的異常

任何Java程式碼都可以丟擲異常,如:自己編寫的程式碼、來自Java開發環境包中程式碼,或者Java執行時系統。無論是誰,都可以通過Java的throw語句丟擲異常。

從方法中丟擲的任何異常都必須使用throws子句。

捕捉異常通過try-catch語句或者try-catch-finally語句實現。

總結來書。Java 規定:對於可查異常必須捕捉、或者宣告丟擲。執行忽略不可查的 RuntimeException 和 Error。

捕獲異常 try、catch 和 finally

  • 首先是 try-catch 語句

基本語法如下:

try {  
    // 可能會發生異常的程式程式碼  
} catch (Type1 id1){  
    // 捕獲並處置try丟擲的異常型別Type1  
} catch (Type2 id2){  
     //捕獲並處置try丟擲的異常型別Type2  
}複製程式碼

關鍵詞 try 後面的大括號區域為可能發生異常的程式碼,稱為監控區。當丟擲異常或者出現執行時異常,然後由 Java 執行時系統巡展匹配 catch 子句。若有匹配的catch子句,則執行其異常處理程式碼,try-catch語句結束。

匹配規則:丟擲的異常屬於 catch 語句捕獲異常類及子類,則匹配成功。

需要注意的是:一旦某個catch捕獲到匹配的異常型別,將進入異常處理程式碼。一經處理結束,就意味著整個try-catch語句結束。其他的catch子句不再有匹配和捕獲異常型別的機會,並且 try 裡面如果有沒有走完的語句也會自動跳過

  • try-catch-finally

這個很簡單,無論 try-catch 是正常結束還是發生異常捕獲,都會執行 finally 語句,如果在 finally 之前發生了 return,那麼 finally 語句塊會在 retrun 之前執行。

小結:

try 塊:用於捕獲異常。其後可接零個或多個catch塊,如果沒有catch塊,則必須跟一個finally塊。
catch 塊:用於處理try捕獲到的異常。
finally 塊:無論是否捕獲或處理異常,finally塊裡的語句都會被執行。當在try塊或catch塊中遇到return語句時,finally語句塊將在方法返回之前被執行。
finally 如果有 return 會覆蓋 catch 裡的throw,同樣如果 finally 裡有 throw 會覆蓋 catch 裡的 return。

try-catch-finally 規則

1.必須在 try 後面新增 catch 或 finally。可以同時存在,catch 塊也可以有多個,但是 catch 必須放在 try 塊或者 catch 塊後面。
2.try-catch-finally 可以巢狀
3.在 try-catch-finally 結構中,可以重新丟擲異常
4.除非 finally 中丟擲異常或者 JVM 停止執行,否則都會執行 finally 語句

try-catch-finally 語句執行順序
1.當 try 沒有捕獲到異常時:try 語句正常執行,程式跳過 catch 語句,執行 finally。

2.當 try 捕獲到異常時,catch 沒有匹配上異常,則交個 JVM 處理,finally 語句塊還是會被執行,但是 finally 之後的語句不會被執行了。

3.當 try 捕獲到異常,catch 匹配上了,則按照 try-catch-finally 的正常順序執行,但是 try 裡面異常語句之後的語句不會被執行。

丟擲異常

任何 Java 程式碼都可以丟擲異常,語句通過 throw 丟擲、方法則是 throws。

為什麼要 throw 異常?

如果一個方法可能會出現異常,但是沒有能力處理這種又一次,則可以在方法宣告處用 throws 語句來丟擲。

throws 語句可以同時宣告丟擲多個異常,語法格式如下

void methodName()throws Exception1,Exception2,Exception3{
}複製程式碼

就醬紫。此時這個方法就不用對方法重點問這些異常做任何處理,但是呼叫這個方法的語句必須要處理,要麼繼續拋,要麼 try-catch。

Throws 丟擲規則

1.如果是不可查異常,那麼不用宣告,編譯仍然可以通過,但是執行時會被JVM 丟擲
2.如果是可查異常,要麼 try-catch,要麼 throws,否則編譯無法通過。
3.當丟擲了異常時,該方法的呼叫者才會處理或者重新丟擲該異常。
4.呼叫 throws 異常的方法,必須 try-catch 呼叫語氣,且 catch 的異常必須是丟擲異常的同類或者父類。

Throw 丟擲異常
這個真沒什麼好說的了,想拋或者需要拋,就直接呼叫就行了,注意 throw 後面只能接 Throwable 的子類。

Throwable 類常用方法

這個本應該在開篇就講的,但是好像都不怎麼常用,所以就放在這裡查漏補缺吧

返回值 方法名 方法說明
Throwable fillInStackTrace() 在異常堆疊跟蹤中填充
Throwable getCause 返回 Throwable 的 cause
String getLocalizedMessage 建立此 Throwable 的本地化描述
String getMessage 返回此 Throwable 的詳細訊息字串
StackTraceElement[] getStackTrace() 提供程式設計訪問由printStackTrace 輸出的堆疊跟蹤資訊
Throwable initCause 將此 throwable 的 cause 初始化為指定值
void printStackTrace 將此 Throwable 及其追蹤輸出至標準錯物流
String toString 返回此 Throwable 的簡短描述

Java 常見的異常

  • 執行時異常
    1.ArrayIndexOutOfBoundException
    陣列索引越界異常。
    2.ArithmeticException
    算術條件異常,如除數為0
    3.NullPointException
    空指標異常。
    4.ClassNotFoundException
    找不到類異常。
    5.NegativeArraySizeException
    陣列長度為負異常
    6.ArrayStoreException
    陣列中包含不相容的值丟擲異常
    7.SecurityException
    安全性異常
    8.IllegalArgumentException
    非法引數異常

  • IOException
    1.IOException
    操作輸入流和輸出流可能出現的異常
    2.EOFException
    檔案已結束異常
    3.FileNotFoundException
    檔案未找到異常

  • 其他
    1.ClassCastException
    型別轉換異常類
    2.ArrayStoreException
    陣列中包含不相容的值丟擲的異常
    3.SQLException
    運算元據庫異常類
    4.NoSuchFieldException
    欄位未找到異常
    5.NoSuchMethodException
    方法未找到丟擲的異常
    6.NumberFormatException
    字串轉換為數字丟擲的異常
    7.StringIndexOutOfBoundsException
    字串索引超出範圍丟擲的異常
    8.IllegalAccessException
    不允許訪問某類異常
    9.InstantiationException
    當應用程式試圖使用Class類中的newInstance()方法建立一個類的例項,而指定的類物件無法被例項化時,丟擲該異常

自定義異常

使用Java 內建的異常類可以描述在程式設計中出現的大部分情況,除此之外,使用者還可以自定義異常。自定義異常類,只需要繼承 Exception 即可。

在程式中使用自定義異常類,大體可分為以下幾個步驟。

1.建立自定義異常類。
2.在方法中通過throw關鍵字丟擲異常物件。
3.如果在當前丟擲異常的方法中處理異常,可以使用try-catch語句捕獲並處理;否則在方法的宣告處通過throws關鍵字指明要丟擲給方法呼叫者的異常,繼續進行下一步操作。
4.在出現異常方法的呼叫者中捕獲並處理異常。

練習

異常差不多就到這裡吧,做完這個小練習,如果能做對的話就差不多了,如果做不對,那再回過頭去重新讀一遍這篇文章。

//執行語句
TestException testException1 = new TestException();
    try {
        testException1.testEx();
    } catch (Exception e) {
        e.printStackTrace();
    }

//異常類
public class TestException {
    public TestException() {
    }

    boolean testEx() throws Exception {
        boolean ret = true;
        try {
            ret = testEx1();
        } catch (Exception e) {
            Log.e("sss___sss","testEx, catch exception");
            ret = false;
            throw e;
        } finally {
            Log.e("sss___sss","testEx, finally; return value=" + ret);
            return ret;
        }
    }

    boolean testEx1() throws Exception {
        boolean ret = true;
        try {
            ret = testEx2();
            if (!ret) {
                return false;
            }
            Log.e("sss___sss","testEx1, at the end of try");
            return ret;
        } catch (Exception e) {
            Log.e("sss___sss","testEx1, catch exception");
            ret = false;
            throw e;
        } finally {
            Log.e("sss___sss","testEx1, finally; return value=" + ret);
            return ret;
        }
    }

    boolean testEx2() throws Exception {
        boolean ret = true;
        try {
            int b = 12;
            int c;
            for (int i = 2; i >= -2; i--) {
                c = b / i;
                Log.e("sss___sss","i=" + i);
            }
            return true;
        } catch (Exception e) {
            Log.e("sss___sss","testEx2, catch exception");
            ret = false;
            throw e;
        } finally {
            Log.e("sss___sss","testEx2, finally; return value=" + ret);
            return ret;
        }
    }
}複製程式碼

求以上語句的答應結果~

自己先讀一遍,再來對答案哦~

sss___sss: i=2
sss___sss: i=1
sss___sss: testEx2, catch exception
sss___sss: testEx2, finally; return value=false
sss___sss: testEx1, finally; return value=false
sss___sss: testEx, finally; return value=false複製程式碼

好了,異常機制的學習就到這裡吧。

相關文章