空指针异常源于调用null引用的成员,需前置防御而非捕获;arrayindexoutofboundsexception是indexoutofboundsexception子类,二者均因越界触发,应通过校验或安全api避免。

NullPointerException 总是发生在你“以为它不为 null”的那一刻
空指针不是配置问题,也不是环境问题,它只说明:你调用了 null 引用的成员——比如 str.length()、list.size()、obj.toString(),或者给 new HashMap() 传了 null 键却没设允许。
常见踩坑点:
- 从数据库或 HTTP 接口返回的对象字段没判空,直接链式调用
user.getAddress().getCity() - 方法参数未用
Objects.requireNonNull()校验,下游一用就崩 - 集合操作中误把
map.get("key")当非空结果处理,而该 key 实际不存在 - 用
Optional包了一层却写成optional.get(),等于白包
真正有效的做法不是 catch,而是前置防御:入参校验用 Objects.requireNonNull(),出参封装用 Optional.ofNullable(),对外 API 明确文档是否允许 null —— 不是“能不能”,而是“该不该”。
ArrayIndexOutOfBoundsException 和 IndexOutOfBoundsException 别再傻傻分不清
ArrayIndexOutOfBoundsException 是数组专属,IndexOutOfBoundsException 是它的父类,也是 ArrayList.get()、String.charAt() 等所有基于索引访问操作的统一出口。它们本质一样:你越界了,JVM 拒绝配合。
典型场景:
for (int i = 0; i —— 多了一个等号,<code>i == list.size()时必然越界- 用
substring(5, 10)处理长度只有 7 的字符串,第二个参数超长 - 从 JSON 解析出
int[] arr后没检查arr != null && arr.length > 0就直接取arr[0]
比 try-catch 更省事的办法:改用增强 for 循环、Stream 的 findFirst() 或 elementAt()(需引入 vavr 或自定义工具),或者在索引访问前加一行 if (i >= 0 && i —— 看似啰嗦,但比堆栈里翻 20 层调用找谁越界快得多。
ClassCastException 其实是 instanceof 忘了写,不是泛型没用好
抛这个异常,90% 是因为你写了类似 (User) obj,而 obj 实际是 Admin 或 Map 这种根本不是 User 子类的东西。泛型擦除后,运行时根本不管你在代码里写了什么 List<string></string>,它只看真实类型。
容易被忽略的细节:
- 用
ObjectInputStream反序列化时,类路径/版本不一致,导致反出来的是旧版 class,强转新版对象就崩 - Spring 的
@Autowired注入了错误的 bean 类型(比如接口有多个实现,没加@Qualifier),转型时失败 - 从
Map中取值后不做instanceof直接强转,而 Map 的 value 可能是任意类型
安全写法永远就两条:if (obj instanceof User) 再转;或者用 Class.cast() 配合泛型工具方法封装,避免裸写括号强转。
IllegalArgumentException 和它的子类(比如 NumberFormatException)是“你传错了”不是“它崩了”
这不是 JVM 或框架的问题,是你调用方式错了。Integer.parseInt("abc") 抛 NumberFormatException(它是 IllegalArgumentException 的子类),是因为字符串根本不是数字格式;Thread.sleep(-1) 抛 IllegalArgumentException,是因为时间不能为负 —— 它们都在明确告诉你:“这个参数,我不认。”
开发中高频误操作:
- 前端传了空字符串或空格字符串给后端,后端直接丢进
Long.parseLong(),没 trim 也没判空 - 用
LocalDateTime.parse("2023-01-01")但没指定 formatter,而默认格式要求带时分秒 - MyBatis 的
<if test="status == 'VALID'"></if>中,status是 null,表达式结果为 false,但你以为只是条件不满足,实际可能漏掉业务逻辑分支
这类异常最该做的,是在 Controller 层用 @Valid + DTO 校验拦截,而不是等到底层工具类炸开才处理。参数合法性,必须在入口处卡死。
Runtime 异常真正的复杂点不在捕获,而在定位“谁该为这个 null / 这个索引 / 这个类型负责”。它不报错在调用点,而藏在上游三四个方法之外。所以别急着加 try-catch,先看堆栈最顶上那一行,再顺藤摸到数据源头。










