丟擲和捕獲異常
| 導航 異常 主題: |
語言編譯器擅長指出程式中大部分錯誤程式碼,但是有一些錯誤只有在程式執行時才會顯現。考慮 程式碼清單 6.1;這裡,程式定義了一個方法 divide,它執行一個簡單的除法運算,以兩個整數作為引數引數並返回它們的除法結果。可以安全地假設,當呼叫 divide(4, 2) 語句時,它將返回數字 2。但是,考慮下一條語句,該語句依賴於提供的命令列引數來生成除法運算。如果使用者提供數字零(0)作為第二個引數怎麼辦?我們都知道零除是不可能的,但編譯器不可能預料到使用者會提供零作為引數。
|
|
這種在程式執行時導致錯誤解釋的異常程式碼通常會導致在 Java 中稱為異常的錯誤。當 Java 直譯器遇到異常程式碼時,它會停止執行並顯示有關發生的錯誤的資訊。此資訊稱為堆疊跟蹤。上面的例子中的 堆疊跟蹤 告訴我們更多關於錯誤的資訊,例如發生異常的執行緒——"main"——異常型別——java.lang.ArithmeticException,一個易於理解的顯示訊息——/ by zero,以及可能發生異常的確切方法和行號。
異常物件
[edit | edit source]前面的異常可以由開發人員顯式建立,就像以下程式碼一樣
|
|
注意,當b 等於零時,沒有返回值。它不是由 Java 直譯器本身生成的 java.lang.ArithmeticException,而是由編碼器建立的異常。結果是一樣的。它向您展示了一個異常是一個物件。它的主要特點是可以丟擲。異常物件必須繼承自 java.lang.Exception。標準異常有兩個建構函式
- 預設建構函式;以及,
- 一個帶字串引數的建構函式,這樣你就可以在異常中放置相關資訊。
程式碼部分 6.1: 使用預設建構函式的異常物件的例項。
new Exception();
|
程式碼部分 6.2: 透過在建構函式中傳遞字串來例項化 Exception 物件。
new Exception("Something unexpected happened");
|
這個字串可以稍後使用各種方法提取,如 程式碼清單 6.2 所示。
你可以使用關鍵字 throw 丟擲任何型別的 Throwable 物件。它會中斷方法。throw 語句之後的任何內容都不會被執行,除非丟擲的異常被 處理。異常物件不會從方法中返回,而是從方法中丟擲。這意味著異常物件不是方法的返回值,呼叫方法也可以被中斷,依此類推……
通常,您會為每種不同型別的錯誤丟擲不同型別的異常。有關錯誤的資訊既表示在異常物件內部,也隱含在異常類名稱中,因此在更大的上下文中的人可以弄清楚如何處理您的異常。通常,唯一的資訊是異常型別,並且沒有有意義的資訊儲存在異常物件中。
Oracle 標準異常類
[edit | edit source]下面的框 6.1 介紹了 java.lang 包中的各種異常類。
try/catch 語句
[edit | edit source]Try/Catch 絕對更好。預設情況下,當丟擲異常時,當前方法會中斷,呼叫方法也會中斷,依此類推,直到main 方法。丟擲的異常也可以使用try/catch 語句捕獲。下面是如何使用try/catch 語句:
程式碼部分 6.3: 將除法運算放入try 塊中。
int a = 4;
int b = 2;
int result = 0;
try {
int c = a / b;
result = c;
} catch(ArithmeticException ex) {
result = 0;
}
return result;
|
已執行的程式碼行已突出顯示。當沒有丟擲異常時,方法流程執行try語句,而不是catch語句。
程式碼部分 6.4:捕獲“除以零”錯誤。
int a = 4;
int b = 0;
int result = 0;
try {
int c = a / b;
result = c;
} catch(ArithmeticException ex) {
result = 0;
}
return result;
|
由於在第 5 行丟擲了異常,第 6 行未執行,但異常被catch語句捕獲,因此執行catch塊。以下程式碼也被執行。請注意,catch語句將異常作為引數。還有第三種情況:當異常不屬於與引數相同的類時
程式碼部分 6.5:未捕獲異常。
int a = 4;
int b = 0;
int result = 0;
try {
int c = a / b;
result = c;
} catch(NullPointerException ex) {
result = 0;
}
return result;
|
就好像沒有try/catch語句一樣。異常被丟擲到呼叫方法。
try/catch語句可以包含多個catch塊,以不同的方式處理不同的異常。每個catch塊必須採用一個不同可丟擲類的引數。丟擲的物件可能匹配多個catch塊,但只有第一個與物件匹配的catch塊將被執行。當且僅當以下條件滿足時,catch 塊將捕獲丟擲的異常
- 丟擲的異常物件與 catch 塊指定的異常物件相同。
- 丟擲的異常物件是 catch 塊指定的異常物件的子型別。
這意味著catch塊的順序很重要。因此,不能將捕獲所有異常的catch塊(以java.lang.Exception作為引數)放在捕獲更具體異常的catch塊之前,因為第二個塊永遠不會被執行。
|
|
在第 14 行,我們使用了一個 **多捕獲** 子句。它從 JDK 7 開始可用。這是多個 **catch** 子句的組合,它允許你在單個處理程式中處理異常,同時保留它們的型別。因此,它們不會被封裝在一個父異常超類中,而是保留它們各自的型別。
你也可以在這裡使用java.lang.Throwable類,因為 **Throwable** 是 *應用程式特定* **Exception** 類的父類。但是,在 Java 程式設計圈中不建議這樣做。這是因為 **Throwable** 恰好也是 *非應用程式特定* **Error** 類的父類,而這些類不打算被顯式處理,因為它們由 JVM 本身處理。
可以在catch塊之後新增一個finally塊。finally塊始終被執行,即使沒有丟擲異常、異常被丟擲並被捕獲,或者異常被丟擲但未被捕獲。它是一個放置應始終在不安全操作(如檔案關閉或資料庫斷開連線)之後執行的程式碼的地方。你可以定義一個沒有catch塊的try塊,但是在這種情況下,它必須後跟一個finally塊。
讓我們檢查以下程式碼
在 程式碼部分 6.7 中,methodC 是無效的。因為methodA 和 methodB 傳遞(或丟擲)異常,所以methodC 必須準備好處理它們。這可以透過兩種方式處理:一個try-catch 塊,它將在方法內部處理異常,以及一個throws 子句,它將反過來將異常丟擲給呼叫者以進行處理。上面的示例將導致編譯錯誤,因為 Java 對異常處理非常嚴格。因此,程式設計師被迫在某個時刻處理任何可能的錯誤情況。
方法可以對異常執行兩件事:透過throws 宣告要求呼叫方法處理它,或者透過try-catch 塊在方法內部處理異常。
要正常工作,原始程式碼可以以多種方式修改。例如,以下
程式碼部分 6.8:捕獲和丟擲異常。
public void methodC() throws CustomException, SomeException {
try {
methodB();
} catch(AnotherException e) {
// Handle caught exceptions.
}
methodA();
}
|
來自methodB 的AnotherException 將在本地處理,而CustomException 和 SomeException 將被丟擲給呼叫者以進行處理。當開發人員必須在這兩個選項之間做出選擇時,大多數開發人員會感到尷尬。這種型別的決定不應在開發時做出。如果你是開發團隊,應該在所有開發人員之間討論,以制定共同的異常處理策略。

