finally块仅保证执行,资源释放依赖完整关闭逻辑:变量需在try外声明为null,每个close()须判空并单独捕获异常,关闭顺序须符合依赖关系(如jdbc中resultset→statement→connection),且finally中不可throw、return或吞异常。

finally块本身不关资源,只保证其中代码执行;能否真正释放连接或流,取决于你写的关闭逻辑是否完整——变量声明位置、判空、异常捕获和关闭顺序,一个都不能少。
变量必须声明在try外部且初始化为null
Connection、PreparedStatement、ResultSet、InputStream等对象如果在try内直接声明赋值,finally就访问不到它们,编译都通不过。正确做法是提前声明并设为null:
- Connection conn = null;
- PreparedStatement stmt = null;
- ResultSet rs = null;
- InputStream is = null;
这样后续在finally中才能安全判断并调用close()。
每个close()都要单独判空 + 单独try-catch
rs.close()、stmt.close()、conn.close()可能各自抛SQLException;is.close()、os.close()可能各自抛IOException。任何一个没捕获,整个清理流程就中断了。
不能共用一个try-catch,也不能省略判空——否则null调用会触发NullPointerException。
- 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块中原本的业务异常,导致错误信息丢失
- return会提前退出方法,跳过后续所有关闭逻辑
- 完全不处理close异常(比如只写catch(Exception e){})会让问题静默,难以排查
建议统一用warn级别日志记录,例如:logger.warn("关闭 Connection 失败", e)。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











