Java throws 關鍵字

throws 關鍵字用於在方法簽名中宣告該方法可能拋出的例外,讓呼叫者知道需要處理這些例外。

基本語法

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

throws vs throw

關鍵字用途位置
throws宣告方法可能拋出的例外方法簽名
throw實際拋出例外物件方法內部
// throws:宣告可能拋出的例外
public void readFile(String path) throws IOException {
    // throw:實際拋出例外
    if (path == null) {
        throw new IOException("路徑不能為 null");
    }
    // ...
}

Checked Exception 必須處理

對於 Checked Exception,必須使用 throws 宣告或 try-catch 處理:

import java.io.*;

public class FileReader {
    // 方法1:使用 throws 宣告
    public String readFile(String path) throws IOException {
        BufferedReader reader = new BufferedReader(new java.io.FileReader(path));
        return reader.readLine();
    }

    // 方法2:使用 try-catch 處理
    public String readFileSafe(String path) {
        try {
            BufferedReader reader = new BufferedReader(new java.io.FileReader(path));
            return reader.readLine();
        } catch (IOException e) {
            return null;
        }
    }
}

如果既不宣告也不處理,會編譯錯誤:

public String readFile(String path) {
    BufferedReader reader = new BufferedReader(new java.io.FileReader(path));
    // ✗ 編譯錯誤:Unhandled exception: java.io.FileNotFoundException
    return reader.readLine();
}

Unchecked Exception 不需要 throws

RuntimeException 及其子類別不需要宣告:

// 不需要 throws
public int divide(int a, int b) {
    if (b == 0) {
        throw new ArithmeticException("除數不能為零");
    }
    return a / b;
}

// 但也可以宣告(用於文件說明)
public int divide2(int a, int b) throws ArithmeticException {
    return a / b;
}

例外傳播

使用 throws 可以將例外傳播給呼叫者:

class DataService {
    public String fetchData() throws IOException {
        // 可能拋出 IOException
        return readFromNetwork();
    }
}

class BusinessLogic {
    private DataService dataService = new DataService();

    // 繼續傳播例外
    public void process() throws IOException {
        String data = dataService.fetchData();
        // 處理資料
    }
}

class Application {
    public static void main(String[] args) {
        BusinessLogic logic = new BusinessLogic();
        try {
            logic.process();
        } catch (IOException e) {
            System.out.println("發生錯誤:" + e.getMessage());
        }
    }
}

多個例外

一個方法可以宣告多個例外:

public void processFile(String path) throws IOException, ParseException {
    String content = readFile(path);     // 可能拋出 IOException
    parseContent(content);                // 可能拋出 ParseException
}

// 呼叫者需要處理所有例外
public void caller() {
    try {
        processFile("data.txt");
    } catch (IOException e) {
        System.out.println("檔案錯誤");
    } catch (ParseException e) {
        System.out.println("解析錯誤");
    }
}

// 或使用多重 catch
public void caller2() {
    try {
        processFile("data.txt");
    } catch (IOException | ParseException e) {
        System.out.println("處理失敗:" + e.getMessage());
    }
}

覆寫方法的 throws 規則

子類別覆寫方法時,不能拋出比父類別更多的 Checked Exception:

class Parent {
    public void method() throws IOException {
        // ...
    }
}

class Child extends Parent {
    // ✓ 可以拋出相同的例外
    @Override
    public void method() throws IOException {
        // ...
    }
}

class Child2 extends Parent {
    // ✓ 可以拋出子類別例外
    @Override
    public void method() throws FileNotFoundException {
        // FileNotFoundException 是 IOException 的子類別
    }
}

class Child3 extends Parent {
    // ✓ 可以不拋出例外
    @Override
    public void method() {
        // ...
    }
}

class Child4 extends Parent {
    // ✗ 不能拋出更廣泛的例外
    @Override
    public void method() throws Exception {
        // 編譯錯誤
    }
}

介面實作的 throws 規則

interface DataReader {
    String read() throws IOException;
}

class FileDataReader implements DataReader {
    @Override
    public String read() throws IOException {  // ✓ 可以
        return "";
    }
}

class SafeDataReader implements DataReader {
    @Override
    public String read() {  // ✓ 可以不拋出
        return "";
    }
}

實際範例

資料驗證

public class Validator {
    public void validateUser(User user) throws ValidationException {
        if (user == null) {
            throw new ValidationException("使用者不能為 null");
        }
        if (user.getName() == null || user.getName().isEmpty()) {
            throw new ValidationException("名稱不能為空");
        }
        if (user.getAge() < 0 || user.getAge() > 150) {
            throw new ValidationException("年齡無效");
        }
    }
}

// 使用
public void registerUser(User user) {
    try {
        validator.validateUser(user);
        userRepository.save(user);
    } catch (ValidationException e) {
        System.out.println("驗證失敗:" + e.getMessage());
    }
}

資料庫操作

public class UserRepository {
    public User findById(int id) throws SQLException {
        Connection conn = getConnection();
        PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
        stmt.setInt(1, id);
        ResultSet rs = stmt.executeQuery();

        if (rs.next()) {
            return new User(rs.getString("name"), rs.getInt("age"));
        }
        return null;
    }

    public void save(User user) throws SQLException {
        Connection conn = getConnection();
        PreparedStatement stmt = conn.prepareStatement(
            "INSERT INTO users (name, age) VALUES (?, ?)"
        );
        stmt.setString(1, user.getName());
        stmt.setInt(2, user.getAge());
        stmt.executeUpdate();
    }
}

網路請求

public class HttpClient {
    public String get(String url) throws IOException, URISyntaxException {
        URI uri = new URI(url);
        HttpURLConnection conn = (HttpURLConnection) uri.toURL().openConnection();
        conn.setRequestMethod("GET");

        int responseCode = conn.getResponseCode();
        if (responseCode != 200) {
            throw new IOException("HTTP 錯誤:" + responseCode);
        }

        BufferedReader reader = new BufferedReader(
            new InputStreamReader(conn.getInputStream())
        );
        StringBuilder response = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            response.append(line);
        }
        reader.close();

        return response.toString();
    }
}

最佳實踐

1. 只宣告會實際拋出的例外

// ✗ 不要過度宣告
public void method() throws Exception, IOException, SQLException {
    // 只會拋出 IOException
}

// ✓ 精確宣告
public void method() throws IOException {
    // ...
}

2. 使用具體的例外類型

// ✗ 太籠統
public void method() throws Exception {
    // ...
}

// ✓ 具體的例外
public void method() throws IOException, ParseException {
    // ...
}

3. 適當的例外文件

/**
 * 讀取檔案內容。
 *
 * @param path 檔案路徑
 * @return 檔案內容
 * @throws FileNotFoundException 如果檔案不存在
 * @throws IOException 如果讀取時發生錯誤
 */
public String readFile(String path) throws FileNotFoundException, IOException {
    // ...
}

重點整理

  • throws 宣告方法可能拋出的例外
  • throw 實際拋出例外物件
  • Checked Exception 必須處理或宣告
  • Unchecked Exception 不需要宣告
  • 覆寫方法不能拋出更多的 Checked Exception
  • 例外傳播讓呼叫者決定如何處理
  • 精確宣告實際會拋出的例外