java中i/o异常必须捕获或声明,因ioexception是受检异常,编译器强制处理;推荐用try-with-resources自动关闭资源,并按需捕获具体子类以实现差异化响应。

Java中I/O异常必须捕获或声明
Java的I/O操作(如FileInputStream、BufferedReader、ObjectOutputStream)抛出的是受检异常(IOException及其子类),编译器强制要求处理——不能忽略,也不能只靠运行时兜底。
常见错误现象:直接调用fileReader.read()却不包在try-catch里,或忘了在方法签名加throws IOException,编译直接报错:Unhandled exception type IOException。
- 要么用
try-catch捕获并处理(如记录日志、返回默认值、提示用户) - 要么在方法声明中用
throws IOException向上委托(适用于高层逻辑更清楚如何应对IO失败的场景) - 注意:
RuntimeException子类(如NullPointerException)不需要强制处理,但I/O异常几乎全是受检的
优先用try-with-resources自动关闭资源
老式写法中手动close()容易遗漏或被异常打断(比如catch块里没写finally,或finally里又抛新异常掩盖原异常),导致文件句柄泄漏、磁盘写入不完整等问题。
Java 7+ 推荐用try-with-resources,只要资源实现AutoCloseable(所有标准I/O类都实现了):
立即学习“Java免费学习笔记(深入)”;
try (FileInputStream fis = new FileInputStream("data.bin");
DataInputStream dis = new DataInputStream(fis)) {
int value = dis.readInt();
} catch (IOException e) {
// 异常处理
}
- 即使
readInt()抛异常,dis和fis也会按声明逆序自动close() - 如果
close()本身也抛异常,它会被抑制(suppressed),可通过e.getSuppressed()获取,主异常仍被抛出 - 别在
try括号里声明未初始化的变量(如BufferedReader br = null;),会编译失败
区分IOException与具体子类做差异化处理
统一捕获IOException简单,但实际中不同原因需要不同响应:磁盘满、网络超时、权限不足、文件被占用……全当一回事容易掩盖问题。
建议按需捕获更具体的子类:
-
FileNotFoundException:路径不存在或无读权限,可引导用户检查路径或创建目录 -
SocketTimeoutException(继承自IOException):网络I/O超时,适合重试而非立即失败 -
EOFException:反序列化时提前遇到流结尾,可能是数据损坏,应拒绝加载 -
InterruptedIOException:线程被中断,通常应恢复中断状态并退出
注意:子类捕获要放在父类之前,否则会被IOException提前截获;同时保留一个兜底的catch (IOException e)处理未预见情况。
避免在finally里重复close或吞掉关键异常
即使用了try-with-resources,仍有代码会手动管理资源(比如遗留系统或特殊封装)。这时finally块里的close()极易出错:
- 对已关闭或为
null的流再调用close()可能抛NullPointerException或IOException - 在
finally里try-catch住close()异常却什么都不做(“吃掉”异常),会让真实错误消失 - 如果
try块已抛异常,finally里再抛异常,原始异常会被覆盖(Java 7前行为)
安全写法是判断非空再关,且不忽略close()异常(除非明确接受风险):
FileInputStream fis = null;
try {
fis = new FileInputStream("log.txt");
// ...
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
// 至少记日志,不要静默吞掉
System.err.println("Failed to close stream: " + e.getMessage());
}
}
}
真正难处理的不是语法,而是判断“这个IO失败到底该重试、降级、告警,还是让用户重选文件”——业务语义永远比异常类型更重要。










