跳轉到

Java基礎:例外處理機制

1. 例外處理簡介

例外處理是一種處理程式執行期間可能發生的錯誤或異常情況的機制,允許開發者以結構化和可控的方式處理錯誤。

例外處理在 Java 中的重要性體現在以下幾個方面:

  1. 錯誤管理:提供統一的方式來處理各種錯誤情況。
  2. 程式流程控制:例外可以改變程式的正常執行流程,確保錯誤得到適當處理。
  3. 分離關注點:允許將錯誤處理程式碼與主要業務邏輯分開,提高程式的可讀性和可維護性。

2. Java 例外階層

Java 的例外處理機制建立在一個層次分明的類別階層上。理解這個階層結構對於有效使用例外處理至關重要。

Throwable 類別

在 Java 中,所有的例外都源自 java.lang.Throwable 類別。Throwable 是例外階層的根,它有兩個主要的子類別:Error 和 Exception。

Error 與 Exception

Error:

  • 表示嚴重的問題,通常是系統層級的錯誤。
  • 例如:OutOfMemoryError、StackOverflowError。
  • 一般來說,程式不應該嘗試捕獲或處理 Error。

Exception:

  • 表示可以被程式處理的問題。
  • 分為兩種主要類型:受檢例外和非受檢例外。

受檢例外與非受檢例外

受檢例外(Checked Exceptions):

  • 繼承自 Exception 類別,但不包括 RuntimeException 的子類別。
  • 編譯器強制要求處理這些例外(通過 try-catch 或 throws 宣告)。
  • 例如:IOException、SQLException。

非受檢例外(Unchecked Exceptions):

  • 包括 Error 和 RuntimeException 及其子類別。
  • 編譯器不強制要求處理這些例外。
  • 例如:NullPointerException、ArrayIndexOutOfBoundsException。

3. try-catch 語句

try-catch 語句是 Java 例外處理的核心機制,它允許我們捕獲和處理程式執行過程中可能發生的例外。

基本語法

try-catch 的基本結構如下:

try {
    // 可能拋出例外的程式碼
} catch (ExceptionType e) {
    // 處理特定類型例外的程式碼
}

多重 catch 區塊

當不同類型的例外需要不同的處理方式時,可以使用多重 catch 區塊:

try {
    // 可能拋出多種例外的程式碼
} catch (IOException e) {
    // 處理 IOException
} catch (SQLException e) {
    // 處理 SQLException
} catch (Exception e) {
    // 處理其他所有類型的例外
}

注意:更具體的例外類型應該放在前面的 catch 區塊中。

finally 區塊

finally 區塊用於定義無論是否發生例外都需要執行的程式碼:

try {
    // 可能拋出例外的程式碼
} catch (Exception e) {
    // 處理例外
} finally {
    // 無論是否發生例外都會執行的程式碼
    // 通常用於清理資源
}

程式碼示例:基本 try-catch 使用

public class ExceptionExample {
    public static void main(String[] args) {
        try {
            int result = divide(10, 0);
            System.out.println("結果:" + result);
        } catch (ArithmeticException e) {
            System.out.println("發生算術錯誤:" + e.getMessage());
        } finally {
            System.out.println("程式執行完畢");
        }
    }

    public static int divide(int a, int b) {
        return a / b;
    }
}

在這個例子中,我們嘗試除以零,這會拋出 ArithmeticException。try-catch 區塊捕獲了這個例外並進行了處理,而 finally 區塊確保了無論發生什麼情況,都會執行最後的清理工作。

4. throw 與 throws 關鍵字

例外處理機制中,throwthrows 是兩個重要的關鍵字,用於不同的目的。

throw 拋出例外

throw 關鍵字用於在程式中主動拋出一個例外。當遇到無法處理的情況時,我們可以使用 throw 來創建並拋出一個例外物件。

語法:

throw new ExceptionType("錯誤訊息");

throws 宣告方法可能拋出的例外

throws 關鍵字用在方法宣告中,表示該方法可能拋出某些類型的例外,而這些例外需要調用者來處理。

語法:

public void someMethod() throws ExceptionType1, ExceptionType2 {
    // 方法內容
}

程式碼示例:使用 throw 和 throws

public class ThrowThrowsExample {
    public static void main(String[] args) {
        try {
            validateAge(15);
        } catch (IllegalArgumentException e) {
            System.out.println("年齡驗證失敗:" + e.getMessage());
        }

        try {
            readFile("nonexistent.txt");
        } catch (IOException e) {
            System.out.println("檔案讀取失敗:" + e.getMessage());
        }
    }

    public static void validateAge(int age) {
        if (age < 18) {
            throw new IllegalArgumentException("年齡必須大於或等於 18");
        }
        System.out.println("年齡驗證通過");
    }

    public static void readFile(String filename) throws IOException {
        // 模擬檔案讀取操作
        throw new IOException("檔案 " + filename + " 不存在");
    }
}

在這個例子中,validateAge 方法使用 throw 來拋出一個 IllegalArgumentException,而 readFile 方法使用 throws 來宣告它可能拋出 IOException

正確使用 throwthrows 可以幫助我們更好地控制程式的流程,並確保例外能夠在適當的地方被處理。

5. 自定義例外

雖然 Java 提供豐富的內建例外類別,但有時我們需要創建自定義例外來更好地表達特定的錯誤情況。自定義例外可以提供更多的上下文資訊,使錯誤處理更加精確和有意義。

創建自定義例外類別

創建自定義例外非常簡單,只需要繼承 Exception 類別(對於受檢例外)或 RuntimeException 類別(對於非受檢例外)。

public class InsufficientFundsException extends Exception {
    private double amount;

    public InsufficientFundsException(double amount) {
        super("餘額不足,缺少 " + amount + " 元");
        this.amount = amount;
    }

    public double getAmount() {
        return amount;
    }
}

何時使用自定義例外

自定義例外適用於以下情況: 1. 當現有的例外類別無法準確描述錯誤情況時。 2. 需要攜帶額外的錯誤相關資訊時。 3. 為了提高程式的可讀性和可維護性,使錯誤處理更加明確。

程式碼示例:自定義例外類別

public class BankAccount {
    private double balance;

    public BankAccount(double initialBalance) {
        this.balance = initialBalance;
    }

    public void withdraw(double amount) throws InsufficientFundsException {
        if (amount > balance) {
            double shortfall = amount - balance;
            throw new InsufficientFundsException(shortfall);
        }
        balance -= amount;
    }

    public static void main(String[] args) {
        BankAccount account = new BankAccount(100);
        try {
            account.withdraw(150);
        } catch (InsufficientFundsException e) {
            System.out.println(e.getMessage());
            System.out.println("缺少金額:" + e.getAmount());
        }
    }
}

在這個例子中,我們創建一個 InsufficientFundsException 來處理銀行帳戶餘額不足的情況。這個自定義例外包含錯誤訊息、缺少的金額資訊,這樣可以使錯誤處理更加具體和方便除錯。

6. try-with-resources 語句

try-with-resources 是 Java 7 引入的一個強大特性,提供一種更簡潔、更安全的方式來處理需要關閉的資源,如檔案、資料庫連接等。

自動資源管理

try-with-resources 語句會自動關閉在括號中宣告的資源,無論 try 區塊是正常結束還是拋出例外。這大大簡化資源管理,並有助於防止資源洩漏。

基本語法:

try (Resource resource = new Resource()) {
    // 使用資源的程式碼
} catch (Exception e) {
    // 處理例外
}

實現 AutoCloseable 介面

要使用 try-with-resources,資源類別必須實現 AutoCloseable 介面。這個介面只有一個方法:close()

public class MyResource implements AutoCloseable {
    public void doSomething() {
        System.out.println("使用資源");
    }

    @Override
    public void close() throws Exception {
        System.out.println("關閉資源");
    }
}

程式碼示例:try-with-resources 使用

public class TryWithResourcesExample {
    public static void main(String[] args) {
        try (MyResource resource = new MyResource()) {
            resource.doSomething();
            throw new RuntimeException("測試例外");
        } catch (Exception e) {
            System.out.println("捕獲例外: " + e.getMessage());
        }
    }
}

在這個例子中,即使拋出例外,MyResourceclose() 方法也會被自動調用。這確保了資源總是被正確關閉,無論程式執行過程中是否發生錯誤。

try-with-resources 語句大大簡化資源管理的程式碼,並提高程式的可靠性,適用於處理檔案、網絡連接、資料庫連接等需要顯式關閉的資源。

7. 例外處理實踐

有效的例外處理能提高程式的穩定性,以及改善程式的可讀性和可維護性。以下是一些例外處理的實踐:

適當的例外粒度

  1. 捕獲具體的例外:盡量捕獲特定類型的例外,而不是籠統地捕獲 Exception。
  2. 避免空的 catch 區塊:總是在 catch 區塊中進行適當的處理或記錄。
try {
    // 可能拋出 IOException 的程式碼
} catch (IOException e) {
    logger.error("檔案操作失敗", e);
    // 適當的錯誤處理
}

記錄例外資訊

使用日誌系統記錄例外資訊,包括例外類型、訊息和堆疊追蹤,對於後續的調試和問題分析非常重要。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

private static final Logger logger = LoggerFactory.getLogger(YourClass.class);

try {
    // 可能拋出例外的程式碼
} catch (Exception e) {
    logger.error("操作失敗", e);
}

不要忽視例外

避免捕獲例外後不做任何處理。如果確實不需要處理,至少要記錄例外資訊。

try {
    // 程式碼
} catch (Exception e) {
    // 不要這樣做
    // e.printStackTrace();

    // 應該這樣做
    logger.error("發生錯誤", e);
    // 可能的錯誤恢復邏輯
}

例外轉譯

當捕獲低層級例外時,考慮將其轉換為有意義的高層級例外,有助於保持 API 的一致性和可理解性。

public void processFile(String filename) throws FileProcessingException {
    try {
        // 檔案處理邏輯
    } catch (IOException e) {
        throw new FileProcessingException("處理檔案 " + filename + " 時發生錯誤", e);
    }
}

本篇文章同步刊載iThome: iThome
筆者個人的網站: JUNYI