Java 7 引入 Try-with-Resources (TWR),將實現 AutoCloseable 的資源綁定在 try 塊作用域,編譯器自動生成 異常安全、無資源洩漏 的釋放代碼。本文深度解析 TWR 語法、底層機制、被壓制異常、多資源關閉順序、常見誤區 及遷移指南。
// 讀取 URL 流寫入文件,需處理多層嵌套、多異常、關閉順序
InputStream is = null;
OutputStream os = null;
try {
is = url.openStream();
os = new FileOutputStream(file);
byte[] buf = new byte[4096];
int len;
while ((len = is.read(buf)) > 0) {
os.write(buf, 0, len);
}
} catch (IOException e) {
// 處理讀寫異常
} finally {
// 關閉順序:後打開先關閉,每個 close 都要 try-catch
if (os != null) try { os.close(); } catch (IOException ignore) {}
if (is != null) try { is.close(); } catch (IOException ignore) {}
}
痛點:代碼冗長、易漏關、異常掩蓋 (close() 拋異常覆蓋原始異常)、複製粘貼極易出錯。
// 資源聲明在 try() 中,隱式 final,編譯器保證關閉
try (OutputStream os = new FileOutputStream(file);
InputStream is = url.openStream()) {
byte[] buf = new byte[4096];
int len;
while ((len = is.read(buf)) > 0) {
os.write(buf, 0, len);
}
} // 隱式 finally: is.close(); os.close(); (逆序)
// 捕獲異常可選,也可繼續拋出
try (Resource r1 = ...; Resource r2 = ...),用分號分隔,隱含 final。java.lang.AutoCloseable (JDK 7) 或 java.io.Closeable (繼承 AutoCloseable)。
public interface AutoCloseable {
void close() throws Exception; // 拋 Exception 而非 IOException,更通用
}
JDK 7+ 所有流、Channel、Connection、Statement、Lock 等均實現此接口。
Resource r1 = null, r2 = null;
try {
r1 = new Resource1();
r2 = new Resource2();
// try 體代碼
} catch (Throwable t) {
// 關閉資源,處理被壓制異常
Throwable primary = t;
try { if (r2 != null) r2.close(); } catch (Throwable s) { primary.addSuppressed(s); }
try { if (r1 != null) r1.close(); } catch (Throwable s) { primary.addSuppressed(s); }
throw primary;
}
// 正常結束也需關閉
finally {
if (primary == null) {
try { if (r2 != null) r2.close(); } catch (Throwable s) { throw s; }
try { if (r1 != null) r1.close(); } catch (Throwable s) { throw s; }
}
}
關鍵點:Throwable.addSuppressed() (JDK 7 新增) 將關閉時的異常附掛到主異常,避免異常丟失。
try (InputStream is = getNullStream()) { // 返回 null
is.available(); // 拋 NPE (主異常)
}
// 輸出堆棧包含 Suppressed: java.lang.NullPointerException (close 時再次拋出)
調用 exception.getSuppressed() 可獲取所有被壓制異常數組,日誌框架自動打印。
// 危險寫法:ObjectInputStream 構造失敗,FileInputStream 不會被關閉!
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("file.bin"))) {
// ...
}
// 安全寫法:分離聲明,確保 FileInputStream 被 TWR 管理
try (FileInputStream fis = new FileInputStream("file.bin");
ObjectInputStream ois = new ObjectInputStream(fis)) {
// ...
}
原則:每個需關閉的資源單獨聲明,避免嵌套構造器。
public class DbConnection implements AutoCloseable {
private Connection conn;
public DbConnection(String url) throws SQLException {
this.conn = DriverManager.getConnection(url);
}
public Connection get() { return conn; }
@Override
public void close() throws SQLException {
if (conn != null) conn.close();
}
}
// 使用
try (DbConnection db = new DbConnection(url)) {
db.get().createStatement().execute("SELECT 1");
}
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
while (rs.next()) { /* ... */ }
} // 自動關閉 rs, ps, conn (逆序)
| 場景 | 建議 |
|---|---|
| 所有 IO 流、數據庫連接、鎖、HTTP 客戶端 | 強制使用 TWR |
| 多資源 | 分開聲明,勿嵌套構造器 |
| 異常處理 | catch 塊記錄主異常,被壓制異常由日誌框架自動輸出 |
| 舊代碼遷移 | 逐步替換 try-catch-finally,優先高頻/高風險模塊 |
| 自定義資源 | 實現 AutoCloseable,close() 內部捕獲異常避免向外拋出 |
Try-with-Resources 是 Java 資源管理的里程碑:**將「必須關閉」的約束從程序員腦海轉移到編譯器保證**。它不僅減少樣板代碼,更從根本上解決了異常掩蓋、資源洩漏、關閉順序錯誤三大頑疾。只要資源實現 AutoCloseable,**無腦加上 try()**,是現代 Java 開發的基本職業素養。