因为流对象构造失败时变量为null,直接调用close()会触发NullPointerException;try-with-resources自动判空、确保关闭且抑制关闭异常,不掩盖主异常。

finally 块里直接调用 close() 为什么有时会报 NullPointerException?
因为 close() 被调用前,流对象本身可能根本没成功创建(比如构造时就抛了 IOException),导致变量仍为 null。直接在 finally 中写 stream.close() 就会触发 NullPointerException。
正确做法是先判空:
InputStream is = null;
try {
is = new FileInputStream("data.txt");
// 读取操作
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
// 记录日志,但不抛出,避免掩盖原始异常
}
}
}
为什么 try-with-resources 比手写 finally 更可靠?
try-with-resources 是 Java 7 引入的语法糖,它自动处理资源声明、隐式调用 close()、并保证即使 try 块中抛异常,close() 仍会被执行——而且它内部已做了非空判断和异常抑制(suppression)处理。
关键点:
- 资源必须实现
AutoCloseable接口(InputStream、OutputStream、Connection等都满足) - 资源声明必须在
try括号内,且不能是null(否则会立即抛NullPointerException) - 如果
close()本身也抛异常,它会被“抑制”并可通过Throwable.getSuppressed()获取,不会覆盖主异常
示例:
try (FileInputStream is = new FileInputStream("data.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
String line = reader.readLine();
} catch (IOException e) {
// 这里捕获的是 try 块里的异常,不是 close() 的
}
finally 中 close() 抛异常会吞掉原始异常吗?
会。如果 try 块已抛出异常 A,而 finally 中 close() 又抛出异常 B,那么 A 会被彻底丢弃,外部只能看到 B——这是极难排查的静默错误。
解决方式只有两个:
- 在
finally的close()外层加try-catch,且不向上抛(如仅打日志) - 改用
try-with-resources,它天然支持异常抑制,原始异常始终是主异常
注意:不要在 finally 里 throw 新异常,也不要 return,这会中断异常传播链。
Android 或低版本 Java(
Java 6 及以下、或部分 Android API level(如 minSdk try-with-resources,只能手写 finally,但必须严格遵循防御模式:
- 每个资源单独声明、单独判空、单独
close() - 多个流嵌套时(如
BufferedInputStream包裹FileInputStream),只需关闭外层流(BufferedInputStream.close()会自动调用底层流的close()) - 使用工具类封装(如 Apache Commons IO 的
IOUtils.closeQuietly())可省去重复判空和空catch
示例(兼容 Java 5):
InputStream is = null;
try {
is = context.getAssets().open("config.json");
parseConfig(is);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
IOUtils.closeQuietly(is); // 不抛异常,不干扰流程
}
手写 finally 最容易被忽略的是资源是否真被初始化、以及 close() 异常对主异常的覆盖——这两个点一旦出错,问题往往延迟暴露,日志里只看到一个无关的 IOException,而真正的业务异常消失了。

















