checked异常编译时强制处理,unchecked异常编译时不检查;前者对应外部不确定性,需显式捕获或声明,后者对应程序逻辑错误,应优先修复而非兜底。

Checked异常编译不通过,Unchecked异常编译直接放行
Java在编译阶段就对两类异常区别对待:只要方法里抛出了IOException、SQLException这类Checked异常,而你既没用try-catch包住,也没在方法签名加throws声明,javac立刻报错,拒绝生成class文件。但NullPointerException、IllegalArgumentException这种Unchecked异常,哪怕你明知道它十有八九会炸,编译器也一声不吭。
这不是疏忽,是设计使然——Checked异常代表“外部世界可能出问题”,比如磁盘坏了、网络断了、数据库连不上;Unchecked异常则代表“代码写错了”,比如调了null对象的方法、数组越界、除零。前者得拦住,后者该修代码,而不是靠catch兜底。
- 常见错误现象:
unreported exception IOException; must be caught or declared to be thrown - 容易踩的坑:把
FileNotFoundException当RuntimeException处理,结果编译不过,临时补个throws Exception糊弄过去,反而掩盖了资源管理责任 - 实操建议:遇到编译报错说“must be caught or declared”,先别急着加
try-catch,想想能不能用try-with-resources自动关流,或者提前校验路径/权限,从源头避免异常发生
Checked异常必须显式声明或捕获,Unchecked异常可捕可不捕
Checked异常的强制性,本质是把“谁负责恢复”这个契约写进方法签名。调用者看到readFile() throws IOException,就知道自己要么接手处理,要么继续上抛。而parseInt(String)只抛NumberFormatException(Unchecked),调用方完全可以当它不存在,直到运行时崩了才看到堆栈。
但这不等于Unchecked异常就该放任自流。生产环境里,NullPointerException频发往往说明空值校验缺失,ArrayIndexOutOfBoundsException反复出现大概率是边界逻辑没想全。
立即学习“Java免费学习笔记(深入)”;
- 使用场景:对外提供SDK或API时,用Checked异常明确告知调用方“这里可能失败,你得准备”;内部工具类、DTO转换等纯内存操作,优先用Unchecked异常快速暴露逻辑缺陷
- 参数差异:Checked异常构造时通常需要带业务上下文信息(如
new IOException("Failed to write to " + path)),而Unchecked异常更倾向用简洁消息(new IllegalArgumentException("age must be > 0")) - 性能影响:Checked异常本身无额外开销,但过度依赖
try-catch包裹正常流程(比如用NumberFormatException控制循环退出)会拖慢执行——异常机制本就不为高频路径设计
自定义异常该继承Exception还是RuntimeException?
关键看你想让使用者“不得不处理”,还是“可以自主决定是否拦截”。继承Exception就是Checked异常,继承RuntimeException就是Unchecked异常——就这么直白,没有中间态。
比如你写了个支付网关客户端,PayTimeoutException应该继承Exception:超时是外部依赖不稳定导致的,调用方理应重试或降级;但InvalidAmountException(金额格式非法)更适合继承RuntimeException:这是上游传参错误,属于开发阶段就该堵死的问题,不该靠运行时catch来兜底。
- 容易踩的坑:把所有自定义异常都塞进
RuntimeException子类,美其名曰“简化调用”,结果上线后支付失败静默吞掉,监控完全失灵 - 实操建议:先问一句——这个异常是“系统外部不可控因素引发的”,还是“当前代码逻辑缺陷导致的”?前者选
Exception,后者选RuntimeException - 兼容性提醒:如果已发布的API抛的是Checked异常,后续改成Unchecked会破坏二进制兼容性(老调用方的
throws声明还在,但编译器不再强制要求),慎改
为什么有些框架(如Spring)把Checked异常转成Unchecked?
Spring的JdbcTemplate、RestTemplate几乎不抛SQLException或IOException,而是统一转成DataAccessException或ResourceAccessException——它们都继承RuntimeException。这不是偷懒,是为了解耦异常语义与具体技术实现。
比如你换数据库从MySQL切到PostgreSQL,SQLException子类变了,但DataAccessException层级不变;又比如你把HTTP调用换成gRPC,底层IOException消失了,但ResourceAccessException仍能统一捕获。开发者不用为每种技术栈写一套catch分支。
- 使用场景:封装跨层调用(DAO→Service→Controller)时,上层不关心下层用的是JDBC还是MyBatis,也不该被
SQLException绑架 - 注意点:框架转换后,原Checked异常的“强制处理”契约消失了,团队得靠Code Review或静态检查(如ErrorProne规则)来确保关键错误没被忽略
- 真实陷阱:有人照搬Spring做法,在自己项目里把
FileNotFoundException也包装成RuntimeException,结果配置文件缺失导致服务启动失败,日志里只有一行Caused by: RuntimeException: file not found,根本看不出是I/O问题
try-catch噪音,重则让故障定位变成猜谜游戏。










