CodeGym /課程 /JAVA 25 SELF /例外作為 API 的一部分與 try-with-resources

例外作為 API 的一部分與 try-with-resources

JAVA 25 SELF
等級 24, 課堂 4
開放

1. 例外作為 API 的一部分

為什麼例外是方法「合約」的一部分?

當你撰寫方法時,你不僅定義參數與回傳值,還定義它可能拋出的例外。這是你的方法與其使用者之間「合約」的一部分。如果方法可能拋出例外,所有呼叫它的人都應該知道 — 好讓他們正確處理錯誤(或至少不會在程式突然因錯誤終止時感到意外)。

範例:

public void readFile(String filename) throws IOException {
    // ... 讀取檔案
}

這裡明確指出該方法可能拋出 IOException。這是給方法使用者的訊號:「請準備好處理讀取檔案的錯誤!」

為例外撰寫文件:Javadoc 中的 @throws 標籤

為了讓其他開發者了解你的方法可能拋出哪些例外,請在 Javadoc 中使用 @throws(或 @exception)標籤。

範例:

/**
 * 讀取檔案內容。
 *
 * @param filename 檔名
 * @return 以字串形式回傳檔案內容
 * @throws IOException 若發生讀取檔案錯誤
 */
public String readFile(String filename) throws IOException {
    // ...
}

為什麼需要這樣做?

  • 幫助其他開發者了解需要處理哪些錯誤。
  • IDE 與文件產生器(例如 Javadoc)會在提示中直接顯示這些例外。
  • 提升程式碼的可靠性與可預測性。

API 中的 Checked 與 Unchecked 例外

Checked 例外(繼承自 Exception,但不是 RuntimeException)是方法合約的一部分。必須要麼加以處理,要麼在方法簽章中明確往外拋出(throws)。

Unchecked 例外(RuntimeException 及其子類)通常表示程式錯誤(例如 NullPointerException、IllegalArgumentException)。它們不一定要在簽章中宣告,但如果你的方法可能拋出這類錯誤(例如引數不正確),也應該在 Javadoc 中加以說明。

範例:

/**
 * 將 a 除以 b。
 * @param a 被除數
 * @param b 除數
 * @return 除法結果
 * @throws IllegalArgumentException 如果 b == 0
 */
public int divide(int a, int b) {
    if (b == 0) throw new IllegalArgumentException("除數不能為 0");
    return a / b;
}

例外與 API 設計

  • 站在方法使用者的角度思考:他可以且應該處理哪些錯誤?哪些是 bug,哪些是「預期的」情境?
  • 不要濫用 Checked 例外:如果錯誤是 bug(例如無效的引數),較好拋出 Unchecked 例外。
  • 為所有可能向外拋出的例外撰寫文件。

2. 結構 try-with-resources

問題:如何安全地關閉資源?

在許多情境下需要使用必須在使用後關閉的資源:檔案、網路連線、資料庫等。如果忘了關閉資源,可能導致記憶體洩漏、檔案鎖定或其他麻煩。

早期作法:

BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("data.txt"));
    String line = reader.readLine();
    // ...
} catch (IOException e) {
    // 錯誤處理
} finally {
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException e) {
            // 關閉時的錯誤處理
        }
    }
}

程式碼很多,容易出錯,也可能忘了關閉資源。

解法:try-with-resources

自 Java 7 起引入了 try-with-resources 結構,它會自動關閉所有資源,即使在區塊內發生例外也一樣。

語法:

try (ResourceType resource = new ResourceType(...)) {
    // 使用資源
} catch (ExceptionType e) {
    // 錯誤處理
}

範例:

try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    System.out.println("讀取檔案時發生錯誤: " + e.getMessage());
}
// reader.close() 會自動被呼叫!

它如何運作?

  • 在 try 後的括號中宣告需要關閉的資源。
  • 離開 try 區塊後(即使發生例外!),會對每個資源呼叫 close() 方法。
  • 這只適用於實作 AutoCloseable(或其父介面 Closeable)的資源。

介面 AutoCloseable:

public interface AutoCloseable {
    void close() throws Exception;
}

所有 Java 標準資源(檔案、串流、資料庫連線)都實作了這個介面。

可以宣告多個資源

try (
    BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
    BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))
) {
    String line;
    while ((line = reader.readLine()) != null) {
        writer.write(line);
        writer.newLine();
    }
}

兩個資源都會自動關閉,即使發生例外亦然。

try-with-resources 的優點

  • 安全:資源一定會被關閉,即使發生錯誤。
  • 精簡:程式碼更少,出錯機率更低。
  • 可讀性:清楚顯示使用了哪些資源,以及它們何時被關閉。

3. 實作:用 try-with-resources 撰寫安全程式碼

範例:讀取檔案

public static void printFirstLine(String filename) {
    try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
        String line = reader.readLine();
        System.out.println("第一行: " + line);
    } catch (IOException e) {
        System.out.println("錯誤: " + e.getMessage());
    }
}

範例:寫入檔案

public static void writeToFile(String filename, String text) {
    try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
        writer.write(text);
    } catch (IOException e) {
        System.out.println("寫入時發生錯誤: " + e.getMessage());
    }
}

範例:自訂資源

如果你撰寫需要關閉的自訂類別,只要實作 AutoCloseable:

public class MyResource implements AutoCloseable {
    @Override
    public void close() {
        System.out.println("資源已關閉!");
    }
}

現在就可以在 try-with-resources 中使用它:

try (MyResource res = new MyResource()) {
    // 使用資源
} 

4. 常見錯誤與 best practices

錯誤 1:忘了關閉資源(未使用 try-with-resources)。
若不使用 try-with-resources,很容易忘記關閉檔案或串流 — 這會導致資源洩漏。

錯誤 2:嘗試對未實作 AutoCloseable 的物件使用 try-with-resources。
如果你的類別沒有實作這個介面,編譯器不允許在 try-with-resources 中使用它。

錯誤 3:未在 API 中記錄例外。
如果你的方法可能拋出例外 — 務必在簽章中標明(throws)並在 Javadoc 中記錄(@throws)。這能幫助他人正確使用你的程式碼。

錯誤 4:捕捉 Exception 而非具體錯誤。
最好只捕捉你實際預期且能處理的那些例外。

1
任務
JAVA 25 SELF, 等級 24, 課堂 4
上鎖
新聞聚合器:自動讀取文章標題
新聞聚合器:自動讀取文章標題
1
任務
JAVA 25 SELF, 等級 24, 課堂 4
上鎖
魔法神器:自動關閉
魔法神器:自動關閉
1
任務
JAVA 25 SELF, 等級 24, 課堂 4
上鎖
科學實驗室:記錄資料中的潛在故障
科學實驗室:記錄資料中的潛在故障
1
任務
JAVA 25 SELF, 等級 24, 課堂 4
上鎖
數位檔案管理員:安全複製古代手稿
數位檔案管理員:安全複製古代手稿
1
問卷/小測驗
例外階層,等級 24,課堂 4
未開放
例外階層
進階的例外處理操作
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION