根本原因是短路求值机制失效导致右侧危险操作执行:逻辑与(&&)仅在左侧为false时跳过右侧,若左侧校验不严(如判空顺序颠倒),右侧空引用等操作便会触发崩溃;误用非短路运算符&更会强制执行右侧。

这本质上是短路求值机制与代码执行顺序共同作用的结果——左侧条件未兜住,右侧就被迫执行,而它恰好含有会立刻崩溃的操作。
根本原因:&& 的“跳过权”只在左侧为 false 时生效
逻辑与(&&)不是“两边都算完再判断”,而是“先算左边,如果为 false,右边直接不碰”。但这个保护机制有个前提:左侧必须真能拦住整个表达式。一旦左侧条件写得不够严密,比如本该判空却漏了、顺序颠倒、或用了非短路运算符,右侧就会暴露在危险环境中。
- 常见错误写法:
obj.getName().length > 0 && obj != null—— 左侧先调用getName(),obj 为 null 就直接抛NullPointerException,根本没机会走到右侧的判空 - 正确写法:
obj != null && obj.getName() != null && obj.getName().length > 0—— 每一步都前置校验,确保后续调用有安全上下文
右侧常藏“雷区”:空引用、未定义方法、越界访问
筛选逻辑里右侧往往写着实际取值或计算操作,比如 item.status == "active"、user.getProfile().email、data[0].id。这些操作本身不报错,但前提是 item、user、data 都非空且结构完整。一旦左侧没拦住,它们就裸奔执行。
- Python 中:
items and items[0].name—— 若items是空列表,items[0]直接触发IndexError - Java Stream 中:
list.stream().filter(x -> x.getId() != null && x.getId().length() > 0)—— 如果getId()返回 null,调用.length()就崩
混淆 & 和 && 会让问题更隐蔽
在 Java、C# 等语言中,& 是位与运算符,也用于布尔运算,但它不短路——无论左边真假,右边一定执行。若误把 && 写成 &,原本该被跳过的危险操作就会强制运行。
- 例如:
user.isLoggedIn() & fetchUserData()—— 即使用户没登录,fetchUserData()仍会被调用,可能因未授权或环境缺失而失败 - 这种错误在静态检查中不易发现,运行时才暴露,调试成本高
防御性写法比事后补救更有效
别依赖“运气”让左侧拦住一切,要把安全假设变成显式动作:
- 优先使用空安全调用(如 Kotlin 的
?.let、Java 的Optional.ofNullable()) - 把易出错的操作封装成带默认值的工具方法,比如
safeGet(list, 0, "default") - 在 Stream filter 中避免链式调用,先解构再判断:
filter(x -> x != null && x.getId() != null).filter(x -> x.getId().length() > 0)











