Java try-with-resources
try-with-resources 是 Java 7 引入的語法,用於自動關閉實作 AutoCloseable 介面的資源,避免資源洩漏。
傳統方式的問題
傳統使用 finally 關閉資源的方式容易出錯:
// 傳統方式:繁瑣且容易出錯
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("file.txt"));
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close(); // close() 也可能拋出例外
} catch (IOException e) {
e.printStackTrace();
}
}
}
try-with-resources 語法
// 簡潔且安全
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
}
// reader 會自動關閉
基本用法
單一資源
try (FileInputStream fis = new FileInputStream("input.txt")) {
int data;
while ((data = fis.read()) != -1) {
System.out.print((char) data);
}
}
多個資源
使用分號分隔,資源按相反順序關閉:
try (FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt")) {
int data;
while ((data = fis.read()) != -1) {
fos.write(data);
}
}
// 關閉順序:fos → fis
Java 9+ 簡化語法
如果資源已經是 final 或 effectively final,可以在 try 外部宣告:
// Java 9+
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
try (reader) { // 不需要重新宣告
System.out.println(reader.readLine());
}
// 多個資源
FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt");
try (fis; fos) {
// 使用資源
}
AutoCloseable 介面
只有實作 AutoCloseable 或 Closeable 的類別才能使用:
public interface AutoCloseable {
void close() throws Exception;
}
public interface Closeable extends AutoCloseable {
void close() throws IOException;
}
常見可自動關閉的類別
- 所有 I/O 流:
InputStream、OutputStream、Reader、Writer - 資料庫連線:
Connection、Statement、ResultSet - 網路資源:
Socket、ServerSocket - 其他:
Scanner、Formatter、ZipFile
自訂 AutoCloseable
public class MyResource implements AutoCloseable {
private String name;
public MyResource(String name) {
this.name = name;
System.out.println(name + " 開啟");
}
public void use() {
System.out.println(name + " 使用中");
}
@Override
public void close() {
System.out.println(name + " 關閉");
}
}
// 使用
try (MyResource res = new MyResource("資源1")) {
res.use();
}
// 輸出:
// 資源1 開啟
// 資源1 使用中
// 資源1 關閉
多個自訂資源
try (MyResource res1 = new MyResource("資源1");
MyResource res2 = new MyResource("資源2")) {
res1.use();
res2.use();
}
// 輸出:
// 資源1 開啟
// 資源2 開啟
// 資源1 使用中
// 資源2 使用中
// 資源2 關閉 ← 後開先關
// 資源1 關閉
例外處理
close() 拋出的例外
如果 try 區塊和 close() 都拋出例外,close() 的例外會被抑制:
public class FailingResource implements AutoCloseable {
@Override
public void close() throws Exception {
throw new Exception("關閉時發生錯誤");
}
}
try (FailingResource res = new FailingResource()) {
throw new Exception("主程式錯誤");
} catch (Exception e) {
System.out.println("主例外:" + e.getMessage());
// 取得被抑制的例外
Throwable[] suppressed = e.getSuppressed();
for (Throwable t : suppressed) {
System.out.println("被抑制的例外:" + t.getMessage());
}
}
// 輸出:
// 主例外:主程式錯誤
// 被抑制的例外:關閉時發生錯誤
只有 close() 拋出例外
try (FailingResource res = new FailingResource()) {
System.out.println("使用資源");
} catch (Exception e) {
System.out.println("例外:" + e.getMessage());
}
// 輸出:
// 使用資源
// 例外:關閉時發生錯誤
實際範例
讀取檔案
public String readFile(String path) throws IOException {
StringBuilder content = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append("\n");
}
}
return content.toString();
}
寫入檔案
public void writeFile(String path, String content) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(path))) {
writer.write(content);
}
}
複製檔案
public void copyFile(String source, String target) throws IOException {
try (InputStream in = new FileInputStream(source);
OutputStream out = new FileOutputStream(target)) {
byte[] buffer = new byte[1024];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
}
}
資料庫操作
public List<User> findAllUsers() throws SQLException {
List<User> users = new ArrayList<>();
String sql = "SELECT id, name, email FROM users";
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setEmail(rs.getString("email"));
users.add(user);
}
}
return users;
}
使用 PreparedStatement
public User findById(int id) throws SQLException {
String sql = "SELECT * FROM users WHERE id = ?";
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, id);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
return new User(
rs.getInt("id"),
rs.getString("name"),
rs.getString("email")
);
}
}
}
return null;
}
網路連線
public String fetchUrl(String urlString) throws IOException {
URL url = new URL(urlString);
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(url.openStream()))) {
StringBuilder result = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
result.append(line);
}
return result.toString();
}
}
比較
| 特性 | try-finally | try-with-resources |
|---|---|---|
| 程式碼量 | 多 | 少 |
| 可讀性 | 較差 | 較好 |
| 關閉順序 | 手動控制 | 自動(後開先關) |
| 例外處理 | 可能遺失例外 | 抑制例外被保留 |
| 錯誤風險 | 容易忘記關閉 | 自動關閉 |
重點整理
- 使用
try-with-resources自動關閉資源 - 資源必須實作
AutoCloseable介面 - 多個資源用分號分隔,後開先關
- Java 9+ 可以使用外部宣告的變數
close()的例外會被抑制,可用getSuppressed()取得- 適用於檔案、資料庫、網路等需要關閉的資源