
Java里用IllegalArgumentException做输入校验最直接
参数不合法时立刻抛出IllegalArgumentException,比返回null或默认值更安全,调用方没法假装没看见问题。
常见错误现象:写了个public void process(String name),但没检查name是否为null或空串,下游突然NullPointerException,堆栈还藏在深层逻辑里。
- 字符串校验优先用
Objects.requireNonNull(name, "name must not be null"),它自带消息且JDK 7+原生支持 - 空字符串、空白字符要单独判断:
if (name == null || name.trim().isEmpty()),别只靠requireNonNull - 数值范围校验别手写
if (age 150),用Preconditions.checkArgument(Guava)或自己封装,避免重复逻辑 - 不要在构造函数里默默修正输入,比如把
null转成""——这会让调用方误以为输入被接受了
自定义异常类型该不该建?看校验意图
不是所有校验都值得建新异常类。只有当「这个错需要被特定捕获并差异化处理」时才建,否则纯增加维护成本。
使用场景举例:API接口层收到非法JSON字段,想统一返回HTTP 400并带字段名提示;这时建个InvalidFieldException比泛用IllegalArgumentException更利于上层做结构化响应。
立即学习“Java免费学习笔记(深入)”;
- 如果只是内部方法校验,用
IllegalArgumentException或IllegalStateException足够 - 新建异常类必须继承
RuntimeException(非检查异常),否则调用方被迫加try-catch或throws,违背快速失败本意 - 异常消息里别拼接用户原始输入(防日志泄露),写成
"invalid status value: '" + status + "' is not allowed"不如"invalid status value"干净
校验位置卡在哪儿?别让无效数据流进核心逻辑
防御式编程的关键不是“多校验”,而是“在校验失效前就拦住”。校验点越靠近入口,问题定位越快,副作用越少。
容易踩的坑:在DAO层才校验ID是否存在,结果Service层已经做了两次状态变更,回滚成本高;或者在Controller里只校验了必填字段,却放行了格式错误的邮箱,等发邮件时才爆javax.mail.internet.AddressException。
- Controller/Endpoint层:校验HTTP参数格式、必填性、长度、正则(如手机号、邮箱)
- Service方法入口:校验业务规则(如“转账金额不能为负”、“订单状态必须是DRAFT”)
- DAO/Repository层:只校验数据库约束无法覆盖的逻辑(如“一个用户最多有5个未支付订单”),别重复校验主键非空
- 不要在getter/setter里做校验——它们可能被序列化框架、反射大量调用,性能和语义都不对
快速失败≠粗暴中断,要考虑调用方怎么接住
抛异常本身很简单,难的是让上游能合理响应。没人希望看到一个IllegalArgumentException导致整个HTTP请求返回500。
性能影响很小,但兼容性要注意:如果老接口突然加校验,旧客户端可能没处理这种异常,直接挂掉。
- Web应用务必配全局异常处理器(如Spring的
@ControllerAdvice),把IllegalArgumentException转成400响应体 - RPC接口(如Dubbo/GRPC)要在文档里明确标注哪些异常是“预期失败”,否则消费方不敢重试
- 异步任务中抛校验异常,得确认消息队列是否支持死信或重投——别让它无限循环重试一个永远过不了的参数
- 单元测试必须覆盖非法输入路径,用
assertThrows验证是否真抛了预期异常,而不是只测“正常流程”
真正难的不是写那行if (x == null) throw new IllegalArgumentException(),是想清楚谁该负责校验、谁该负责兜底、以及错误消息里哪部分能暴露给外部——这些边界一旦模糊,快速失败就变成快速甩锅。











