throw抛异常的核心目的是明确传递校验失败语义,避免静默错误;应使用具体异常实例如new IllegalArgumentException("userId must be positive"),按参数非法选IllegalArgumentException、状态不支持选IllegalStateException。

Java里用throw抛异常,不是为了“出错”,而是为了“说清楚”
主动抛异常的核心目的,是把校验失败的语义明确传递出去,而不是等后续代码用null或非法值崩掉。比如传入userId为负数,你不抛,调用方可能以为数据写进数据库了,其实根本没处理。
常见错误现象:NullPointerException在深层方法里才爆出,堆栈长、定位难;或者返回null让上层自己判,结果有人忘了判,逻辑静默出错。
- 必须用
throw+ 具体异常实例,不能只写类名(如throw IllegalArgumentException❌) - 推荐用带消息的构造器:
throw new IllegalArgumentException("userId must be positive") - 如果校验逻辑复用多,封装成工具方法(如
Validate.positive(userId, "userId")),但别为了封装而丢掉原始上下文
参数校验时,该选IllegalArgumentException还是IllegalStateException
看校验对象是谁:参数本身不合法,用IllegalArgumentException;参数没问题,但当前对象状态不支持这个操作,才用IllegalStateException。
例如:userService.deleteUser(-1) —— -1作为参数天生无效,抛IllegalArgumentException;connection.executeQuery("SELECT *")在连接已关闭时调用 —— 参数SQL没问题,是connection状态不对,才抛IllegalStateException。
-
IllegalArgumentException是参数校验的默认选择,JDK自身大量使用(如Objects.requireNonNull内部就抛它) - 不要用
RuntimeException裸抛,丢失语义;也别一概用自定义异常,除非业务需要统一捕获或日志分类 - Spring的
@Valid走的是声明式校验,和手写throw不冲突,但别混用同一处:既写if (x 又加@Min(0),维护成本翻倍
性能影响几乎可以忽略,但别在循环里反复抛
抛异常本身开销不大,真正贵的是填充堆栈(fillInStackTrace)。日常参数校验中,一次请求最多抛几次,完全无感。
容易踩的坑是:把throw写在高频循环里,比如解析几千行CSV时每行都校验并抛异常。这时实际不是“校验问题”,而是“设计问题”——应该先收集所有错误再统一报,或改用返回Optional/Result类型。
- 确认是否真需要立刻中断:日志上报、RPC入参、配置加载这类场景,早抛早止损
- 避免在
toString()、hashCode()等基础方法里抛异常,这些方法可能被容器/框架隐式调用,导致不可预期中断 - 如果用Lombok的
@NonNull,它生成的是Objects.requireNonNull,底层也是throw new NullPointerException,语义稍弱,建议手动用IllegalArgumentException更准确
和throws声明的关系:不强制要求,但要对调用方诚实
throw的是运行时异常(RuntimeException及其子类),方法签名不用写throws;但如果你抛的是检查异常(如IOException),就必须声明。参数校验几乎全用运行时异常,所以throws通常不需要加。
不过,不写不等于不用考虑。调用方得知道这里可能崩——靠文档?靠经验?都不如代码直白。所以建议:在JavaDoc里写明可能抛什么异常,尤其是自定义异常;IDE能自动提示,比翻文档快得多。
- 别为了“不写throws”而硬把检查异常包装成运行时异常(如
throw new RuntimeException(e)),会丢失原始类型和处理意图 - 如果团队约定所有参数异常都继承自某个基类(如
BadRequestException),确保它仍是RuntimeException子类,否则接口方法签名会被迫污染 - 最易被忽略的一点:日志框架(如Logback)默认不打印异常堆栈的全部帧,若只看第一行错误信息,可能误以为是空指针——记得检查日志配置是否启用了
%ex或%xEx
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











