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 而非具體錯誤。
最好只捕捉你實際預期且能處理的那些例外。
GO TO FULL VERSION