跳轉到內容

丟擲和捕獲異常

75% developed
來自 Wikibooks,為開放世界提供開放書籍

導航 異常 主題: v  d  e )


語言編譯器擅長指出程式中大部分錯誤程式碼,但是有一些錯誤只有在程式執行時才會顯現。考慮 程式碼清單 6.1;這裡,程式定義了一個方法 divide,它執行一個簡單的除法運算,以兩個整數作為引數引數並返回它們的除法結果。可以安全地假設,當呼叫 divide(4, 2) 語句時,它將返回數字 2。但是,考慮下一條語句,該語句依賴於提供的命令列引數來生成除法運算。如果使用者提供數字零(0)作為第二個引數怎麼辦?我們都知道零除是不可能的,但編譯器不可能預料到使用者會提供零作為引數。

Computer code 程式碼清單 6.1: SimpleDivisionOperation.java
public class SimpleDivisionOperation {
  public static void main(String[] args) {
    System.out.println(divide(4, 2));
    if (args.length > 1) {
      int arg0 = Integer.parseInt(args[0]);
      int arg1 = Integer.parseInt(args[1]);
      System.out.println(divide(arg0, arg1));
    }
  }

  public static int divide(int a, int b) {
    return a / b;
  }
}
Standard input or output 程式碼清單 6.1 的輸出
$ java SimpleDivisionOperation 1 0
2
Exception in thread "main" java.lang.ArithmeticException: / by zero
     at SimpleDivisionOperation.divide(SimpleDivisionOperation.java:12)
     at SimpleDivisionOperation.main(SimpleDivisionOperation.java:7)

這種在程式執行時導致錯誤解釋的異常程式碼通常會導致在 Java 中稱為異常的錯誤。當 Java 直譯器遇到異常程式碼時,它會停止執行並顯示有關發生的錯誤的資訊。此資訊稱為堆疊跟蹤。上面的例子中的 堆疊跟蹤 告訴我們更多關於錯誤的資訊,例如發生異常的執行緒——"main"——異常型別——java.lang.ArithmeticException,一個易於理解的顯示訊息——/ by zero,以及可能發生異常的確切方法和行號。

異常物件

[edit | edit source]

前面的異常可以由開發人員顯式建立,就像以下程式碼一樣

Computer code 程式碼清單 6.2: SimpleDivisionOperation.java
public class SimpleDivisionOperation {
  public static void main(String[] args) {
    System.out.println(divide(4, 2));
    if (args.length > 1) {
      // Convert a string to an integer
      int arg0 = Integer.parseInt(args[0]);
      int arg1 = Integer.parseInt(args[1]);
      System.out.println(divide(arg0, arg1));
    }
  }

  public static int divide(int a, int b) {
    if (b == 0) {
      throw new ArithmeticException("You can\'t divide by zero!");       
    } else {
      return a / b;
    }
  }
}
Standard input or output 程式碼清單 6.2 的輸出
$ java SimpleDivisionOperation 1 0
2
Exception in thread "main" java.lang.ArithmeticException: You can't divide by zero!
at SimpleDivisionOperation.divide(SimpleDivisionOperation.java:14)
at SimpleDivisionOperation.main(SimpleDivisionOperation.java:8)

注意,當b 等於零時,沒有返回值。它不是由 Java 直譯器本身生成的 java.lang.ArithmeticException,而是由編碼器建立的異常。結果是一樣的。它向您展示了一個異常是一個物件。它的主要特點是可以丟擲。異常物件必須繼承自 java.lang.Exception。標準異常有兩個建構函式

  1. 預設建構函式;以及,
  2. 一個帶字串引數的建構函式,這樣你就可以在異常中放置相關資訊。
Example 程式碼部分 6.1: 使用預設建構函式的異常物件的例項。
new Exception();
Example 程式碼部分 6.2: 透過在建構函式中傳遞字串來例項化 Exception 物件。
new Exception("Something unexpected happened");

這個字串可以稍後使用各種方法提取,如 程式碼清單 6.2 所示。

你可以使用關鍵字 throw 丟擲任何型別的 Throwable 物件。它會中斷方法。throw 語句之後的任何內容都不會被執行,除非丟擲的異常被 處理。異常物件不會從方法中返回,而是從方法中丟擲。這意味著異常物件不是方法的返回值,呼叫方法也可以被中斷,依此類推……

通常,您會為每種不同型別的錯誤丟擲不同型別的異常。有關錯誤的資訊既表示在異常物件內部,也隱含在異常類名稱中,因此在更大的上下文中的人可以弄清楚如何處理您的異常。通常,唯一的資訊是異常型別,並且沒有有意義的資訊儲存在異常物件中。

Oracle 標準異常類

[edit | edit source]

下面的框 6.1 介紹了 java.lang 包中的各種異常類。

框 6.1: Java 異常類

Throwable
Throwable 類是 Java 語言中所有錯誤和異常的超類。只有此類(或其子類之一)的例項物件才會被 Java 虛擬機器丟擲或可以被 Java throw 語句丟擲。
Throwable 包含其執行緒在建立時執行堆疊的快照。它還可以包含一個訊息字串,提供有關錯誤的更多資訊。最後,它可以包含一個原因:另一個導致此 Throwable 被丟擲的 Throwable。原因工具是在 1.4 版本中新增的。它也被稱為鏈式異常工具,因為原因本身可以有一個原因,依此類推,導致一系列異常,每個異常都由另一個異常引起。
Error
Error 指示嚴重問題,合理的應用程式不應該嘗試處理這些問題。大多數此類錯誤都是異常情況。
Exception
Exception 類及其子類是 Throwable 的一種形式,它指示合理的應用程式可能希望處理的條件。這也是程式設計師在新增業務邏輯異常時可能要擴充套件的類。
RuntimeException
RuntimeException 是那些可能在 Java 虛擬機器正常執行期間丟擲的異常的超類。方法不需要在可能在方法執行期間丟擲但未捕獲的 RuntimeException 的任何子類中宣告其 throws 子句。
圖 6.2: JCL 中的異常類及其繼承模型。

try/catch 語句

[edit | edit source]

Try/Catch 絕對更好。預設情況下,當丟擲異常時,當前方法會中斷,呼叫方法也會中斷,依此類推,直到main 方法。丟擲的異常也可以使用try/catch 語句捕獲。下面是如何使用try/catch 語句:

Example 程式碼部分 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語句。

Example 程式碼部分 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語句將異常作為引數。還有第三種情況:當異常不屬於與引數相同的類時

Example 程式碼部分 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塊之前,因為第二個塊永遠不會被執行。

Example 程式碼部分 6.6:使用 catch 塊進行異常處理。
try {
  // Suppose the code here throws any exceptions,
  // then each is handled in a separate catch block.

  int[] tooSmallArray = new int[2];
  int outOfBoundsIndex = 10000;
  tooSmallArray[outOfBoundsIndex] = 1;

  System.out.println("No exception thrown.");
} catch(NullPointerException ex) {
  System.out.println("Exception handling code for the NullPointerException.");
} catch(NumberFormatException ex) {
  System.out.println("Exception handling code for the NumberFormatException.");
} catch(ArithmeticException | IndexOutOfBoundsException ex) {
  System.out.println("Exception handling code for ArithmeticException"
    + " or IndexOutOfBoundsException.");
} catch(Exception ex) {
  System.out.println("Exception handling code for any other Exception.");
}
Standard input or output 程式碼部分 6.6 的輸出
Exception handling code for ArithmeticException or IndexOutOfBoundsException.

在第 14 行,我們使用了一個 **多捕獲** 子句。它從 JDK 7 開始可用。這是多個 **catch** 子句的組合,它允許你在單個處理程式中處理異常,同時保留它們的型別。因此,它們不會被封裝在一個父異常超類中,而是保留它們各自的型別。

你也可以在這裡使用java.lang.Throwable類,因為 **Throwable** 是 *應用程式特定* **Exception** 類的父類。但是,在 Java 程式設計圈中不建議這樣做。這是因為 **Throwable** 恰好也是 *非應用程式特定* **Error** 類的父類,而這些類不打算被顯式處理,因為它們由 JVM 本身處理。

finally

[編輯 | 編輯原始碼]

可以在catch塊之後新增一個finally塊。finally塊始終被執行,即使沒有丟擲異常、異常被丟擲並被捕獲,或者異常被丟擲但未被捕獲。它是一個放置應始終在不安全操作(如檔案關閉或資料庫斷開連線)之後執行的程式碼的地方。你可以定義一個沒有catch塊的try塊,但是在這種情況下,它必須後跟一個finally塊。

異常處理示例

[編輯 | 編輯原始碼]

讓我們檢查以下程式碼

Warning 程式碼部分 6.7:處理異常。
public void methodA() throws SomeException {
    // Method body
}

public void methodB() throws CustomException, AnotherException {
    // Method body
}

public void methodC() {
    methodB();
    methodA();
}

程式碼部分 6.7 中,methodC 是無效的。因為methodAmethodB 傳遞(或丟擲)異常,所以methodC 必須準備好處理它們。這可以透過兩種方式處理:一個try-catch 塊,它將在方法內部處理異常,以及一個throws 子句,它將反過來將異常丟擲給呼叫者以進行處理。上面的示例將導致編譯錯誤,因為 Java 對異常處理非常嚴格。因此,程式設計師被迫在某個時刻處理任何可能的錯誤情況。

方法可以對異常執行兩件事:透過throws 宣告要求呼叫方法處理它,或者透過try-catch 塊在方法內部處理異常。

要正常工作,原始程式碼可以以多種方式修改。例如,以下

Example 程式碼部分 6.8:捕獲和丟擲異常。
public void methodC() throws CustomException, SomeException {
  try {
    methodB();
  } catch(AnotherException e) {
    // Handle caught exceptions.
  }
  methodA();
}

來自methodBAnotherException 將在本地處理,而CustomExceptionSomeException 將被丟擲給呼叫者以進行處理。當開發人員必須在這兩個選項之間做出選擇時,大多數開發人員會感到尷尬。這種型別的決定不應在開發時做出。如果你是開發團隊,應該在所有開發人員之間討論,以制定共同的異常處理策略。

關鍵字參考

[編輯 | 編輯原始碼]


Clipboard

待辦事項
新增一些與 變數 中的練習類似的練習


華夏公益教科書