异常清理不能依赖构造块,而应通过嵌套try-finally、final字段判空清理或优先使用try-with-resources实现,确保仅清理已成功创建的资源。

构造块本身不能直接用于异常清理——它和构造方法一样,在对象初始化流程中执行,一旦构造方法抛出异常,整个初始化过程就中断,后续代码(包括普通初始化块)不会继续运行。真正起作用的是在构造方法内部手动管理资源生命周期,核心思路是:谁创建、谁负责清理;未成功创建的部分,绝不能清理。
用嵌套 try-finally 逐层保护可清理资源
这是最可靠、被多个权威资料验证的实战方式。关键不是靠“构造块”,而是把每个可能失败且需清理的资源,用独立的 try-finally 包裹,确保只清理已成功创建的对象。
- 每 new 一个需要 dispose() 或 close() 的对象,立刻跟一个 try-finally 块
- finally 中调用其清理方法,且顺序与创建顺序相反
- 如果该资源创建时抛异常,对应 finally 不会执行(因为 try 没进入),避免对 null 或未初始化对象调用 close()
例如:
try {FileReader fr = new FileReader("a.txt");
try {
BufferedReader br = new BufferedReader(fr);
try {
// 正常业务逻辑
} finally {
br.close(); // 只有 br 成功创建才执行
}
} catch (Exception e) {
fr.close(); // br 创建失败,但 fr 已建好,必须关
throw e;
} finally {
fr.close(); // fr 成功创建才执行
}
} catch (FileNotFoundException e) {
// fr 都没建好,什么也不关
throw e;
}
把资源声明为 final 字段 + 构造中分步赋值 + 显式判空清理
适用于字段级资源管理。将资源字段声明为 final,但在构造中分阶段赋值,并在 finally 中按“是否非 null”判断是否清理。
- final BufferedReader in;
- 构造中先 new FileReader,再用它 new BufferedReader —— 每步都可能失败
- 在 catch 或 finally 中,只对已成功赋值的字段调用 close(),其余跳过
注意:不要在 finally 里无条件调 in.close(),必须加 if (in != null) 判断。
用 try-with-resources 替代手动 close(推荐优先级最高)
如果资源类型实现 AutoCloseable(如 FileReader、BufferedReader、Socket 等),直接用 try-with-resources 是最简洁安全的方式。
- 资源在 try 括号内声明并初始化,JVM 自动保证:只要初始化成功,无论是否异常,都会调用 close()
- 未初始化成功的资源,close() 根本不会触发,完全规避空指针或非法状态
- 无需手写嵌套、无需判空、无需 finally 块
示例:
public InputFile(String fname) throws IOException {try (FileReader fr = new FileReader(fname);
BufferedReader br = new BufferedReader(fr)) {
this.in = br; // 注意:br 是局部变量,不能直接赋给 this.in
// 实际中建议封装为内部资源持有类,或改用组合模式
}
}
避免在构造中调用子类可重写方法
这不是清理问题,但会间接导致清理失败——比如子类重写的 init() 方法里试图访问未初始化的字段,抛出 NullPointerException,让本来能 clean up 的逻辑直接中断。
- 父类构造器中禁止调用 protected/public 方法
- 把初始化逻辑移到 final 方法、private 方法,或通过静态工厂 + 参数校验前置完成
- 这样能防止“半初始化对象”状态下触发额外异常,保障清理路径可控

















