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
- 例外傳播讓呼叫者決定如何處理
- 精確宣告實際會拋出的例外