Ja va 中的 RuntimeException 是非受检异常,编译时不强制你处理,但它在运行时冷不丁冒出来,往往意味着代码逻辑有硬伤。排查这类问题的思路,其实不在于“怎么捕获”,而在于“怎么预防”和“怎么快速定位”。下面按常见子类逐一拆解,结合实际场景聊一聊。
空指针异常(NullPointerException)
这是最老熟人的异常了——本质就是你调了一个 null 对象的方法或字段。排查时先看日志堆栈的第一行,定位到报错位置,弄清楚是哪个变量为 null。然后顺着调用链往回追:这个变量是从哪来的?是方法参数没传对?对象属性没初始化?还是某个方法的返回值直接用了没判空?
主动防御也很简单:对可能为 null 的入参,用 Objects.requireNonNull(str, "str must not be null") 提前拦截;对返回值用 Optional 包装;IDE 的 @NotNull 注解也可以辅助静态检查。习惯养成之后,空指针出现的概率会大幅下降。
数组/字符串索引越界(ArrayIndexOutOfBoundsException / StringIndexOutOfBoundsException)
下标跑到合法范围之外(小于 0 或者大于等于 length),就会抛出这个异常。报错信息里通常会直接给出索引值,比如 Index: 5, Size: 3,一眼就能看出越界了多少。接着检查循环条件:是不是用了 <= 而不是 <?有没有忘记判断 list.size() > 0?
更根本的做法是避免硬编码下标,多用 for-each 或 stream 替代传统 for 循环,这类问题几乎就能杜绝。
类型转换与参数异常(ClassCastException / IllegalArgumentException / NumberFormatException)
这三个异常经常扎堆出现:错误传参 → 解析失败 → 强转崩溃,一条链上的问题。排查时各有侧重:
- ClassCastException:看报错提示 “cannot cast A to B”,重点检查泛型擦除、集合原始类型、JSON 反序列化时的类型声明。
- IllegalArgumentException:JDK 内置方法里常见(比如
Thread.sleep(-1)),也用于自定义校验。排查时优先查入参来源——前端传值?配置文件?还是数据库字段? - NumberFormatException:典型场景是
Integer.parseInt("abc")。建议统一用StringUtils.isNumeric()或正则预校验,而不是依赖 catch 去兜底。
算术与状态类异常(ArithmeticException / IllegalStateException / UnsupportedOperationException)
这类异常往往暴露的是设计或调用时机的问题。
- ArithmeticException(除零):关键计算前加
if (divisor == 0)判断,或者封装一个安全除法工具方法,别等运行时崩溃。 - IllegalStateException:比如迭代器已经遍历完了还调
next(),或者对象还没初始化就调业务方法。需要检查对象的生命周期和状态机流转。 - UnsupportedOperationException:常见于
Arrays.asList()返回的不可变列表上调用add()。排查时注意集合的创建方式,必要时用new ArrayList(list)包装一下。
说到底,RuntimeException 的排查思路就是“预防为主,定位为辅”。把防御性编程的习惯融入日常,配合日志堆栈的快速定位,这类问题就不会成为噩梦。