ioexception是受检异常,编译器强制要求try-catch或throws,否则编译失败;而nullpointerexception等非受检异常编译期不检查,仅运行时抛出。

因为IOException是受检异常,Java编译器在编译阶段就强制要求你表明态度:要么当场处理,要么明确声明风险。不写try-catch也不加throws,代码根本通不过编译。
受检异常的编译期“硬拦截”
Java把异常分成两类,关键区别就在编译器是否插手:
- 受检异常(如IOException、SQLException)——继承Exception但不是RuntimeException子类,编译器会逐行检查:只要调用了可能抛它的API(比如
new FileReader("x.txt")),又没包进try-catch,也没在方法签名写throws IOException,javac立刻报错:
error: unreported exception IOException; must be caught or declared to be thrown - 非受检异常(如NullPointerException、ArrayIndexOutOfBoundsException)——属于RuntimeException分支,编译器完全不管,代码照常编译,只在运行时才暴露问题
为什么偏偏对IO操作这么“较真”?
设计者认为I/O失败不是程序写错了,而是环境不可靠(磁盘满了、网络断了、文件被删了),这类问题可预见、可恢复,不该被忽略:
- 如果IOException是运行时异常,大量read/write调用可能静默失败——表面没报错,实际数据没落盘、请求没发出去
- 强制显式处理,等于逼你在代码里留下决策痕迹:是重试?切备用路径?还是告诉用户“文件找不到了”?
- FileNotFoundException、SocketTimeoutException这些子类,同样继承自IOException,也必须一视同仁地处理
throws和try-catch不是替代关系,是职责分工
选哪个,取决于你在哪一层、能做什么决定:
-
底层工具方法(比如封装一个读JSON的静态工具)适合用
throws IOException:它不假设上层怎么恢复,只负责暴露风险 - 业务入口点(比如Spring Controller的接口方法)通常得用try-catch:要记录日志、返回HTTP 500或友好提示,不能把异常一路甩给前端
- 注意:try-with-resources只保证资源自动关闭,
reader.readLine()抛的IOException仍需捕获或声明,不能以为加了try (Resource r = ...)就万事大吉
常见踩坑提醒
看似处理了,其实等于没做:
-
catch (IOException e) { e.printStackTrace(); }——控制台输出在生产环境基本看不到,也没恢复动作,属于假装处理 - 在循环里对每次write()单独套try-catch——某次写失败被吞掉,后续数据继续写,结果部分丢失却无感知
- lambda里直接调用Files.readAllBytes()——它抛受检异常,但lambda无法声明throws,编译直接失败,得包装成自定义运行时异常或改用其他方式











