finally块不自动释放资源,仅保证执行;真正释放依赖严谨逻辑:变量须在try外声明为null、每个close()需判空并单独try-catch、按依赖反序关闭(如resultset→statement→connection)、finally中不可throw或return。

finally 块本身不释放资源,它只保证其中的代码一定会执行(除非 JVM 强制退出)。真正让资源“必定释放”的,是你在 finally 里写的关闭逻辑是否严谨——变量声明位置、判空、异常捕获、关闭顺序,缺一不可。
变量必须在 try 外声明并初始化为 null
Connection、InputStream、ResultSet 等对象如果在 try 内直接声明赋值,比如:
try {
Connection conn = DriverManager.getConnection(...);
}
那么 finally 就访问不到 conn,编译直接报错。正确写法是提前声明并设为 null:
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
这样后续才能在 finally 中安全判断和调用 close()。
每个 close() 都要单独判空 + 单独 try-catch
rs.close()、stmt.close()、conn.close() 各自可能抛 SQLException;InputStream.close() 可能抛 IOException。任何一个没捕获,整个清理流程就中断了。
- 不能共用一个 try-catch,否则一个失败会跳过后续关闭
- 不能省略判空,否则 null 调用会触发 NullPointerException,掩盖原始异常
- 每个 close() 失败都应记录日志,但不 throw、不 return
典型写法:
if (rs != null) {
try { rs.close(); } catch (SQLException e) { logger.warn("关闭 ResultSet 失败", e); }
}
if (stmt != null) {
try { stmt.close(); } catch (SQLException e) { logger.warn("关闭 Statement 失败", e); }
}
if (conn != null) {
try { conn.close(); } catch (SQLException e) { logger.warn("关闭 Connection 失败", e); }
}
关闭顺序必须符合资源依赖关系
资源之间有明确持有关系,反序关闭极易失败:
- JDBC 场景:ResultSet → Statement → Connection(ResultSet 依赖 Statement,Statement 依赖 Connection)
- IO 流场景:BufferedReader → InputStreamReader → FileInputStream(上层流依赖下层流)
如果先关 Connection,再关 ResultSet,大概率抛 “connection closed” 异常;先关底层流,包装流再调 close() 也会出错。
finally 里严禁 throw、return 或吞异常
即使某个 close() 抛异常,也只记录日志,不 throw 新异常,更不 return:
- throw 会覆盖 try/catch 中原本的业务异常,导致根因丢失
- return 会提前退出方法,跳过后续所有关闭逻辑
- 空 catch 并吞掉异常(如 catch(Exception e){})会让问题难以发现
真正的清理目标不是“零异常”,而是“所有资源都尝试释放”,且主流程错误不被掩盖。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











