应优先在方法入口用Objects.requireNonNull()校验非空、返回值用Optional明确表达可选性、配合@NonNull/@Nullable注解静态检查,并警惕字符串/集合的假安全操作。

用 Objects.requireNonNull() 主动暴露问题
空指针异常(NullPointerException)往往在深层调用中才爆发,排查成本高。与其等运行时崩溃,不如在方法入口就做显式校验。Objects.requireNonNull() 是 JDK 7+ 提供的轻量级工具,它会在参数为 null 时立即抛出带消息的 NullPointerException,堆栈指向调用处而非深层。
- 适用于构造函数、
public方法参数校验,尤其当该参数后续会被多次解引用时 - 比手写
if (x == null) throw new NullPointerException()更简洁,且支持自定义提示消息:Objects.requireNonNull(str, "str must not be null") - 注意:它不阻止
null进入私有方法或内部逻辑,仅作为契约声明——调用方必须保证非空
用 Optional 明确表达“可能为空”的语义
Optional 不是用来“避免 NPE”的银弹,而是把“是否为空”从隐式状态变成显式类型。它强制调用方思考空值路径,而不是侥幸跳过判断。
- 只用于**返回值**场景,例如
findUserById(Long id)应返回Optional,而非User或null - 禁止将
Optional用作字段、方法参数或集合元素——这违背其设计初衷,且会引发Optional嵌套(如Optional)等反模式> - 链式调用安全:可用
map()、flatMap()、orElse()等避免显式isPresent()判断,但慎用get()(仍可能抛 NPE)
用 @NonNull 和 @Nullable 注解统一团队契约
注解本身不运行,但配合 IDE(IntelliJ / Eclipse)和静态分析工具(如 SpotBugs、ErrorProne),能在编码阶段标出潜在空值风险。
- 推荐使用
org.jetbrains.annotations.NotNull和@Nullable(JetBrains 注解兼容性好,无需额外依赖) - 标注位置要一致:参数、返回值、字段都应覆盖;未标注的不等于“可为空”,而是“未声明”,IDE 默认按不可为空处理
- 与 Lombok 配合时注意:
@RequiredArgsConstructor不会自动为@NonNull字段生成空检查,需手动加@NonNull到字段上,Lombok 才生成Objects.requireNonNull()
警惕字符串和集合的“假安全”操作
很多开发者以为 String.isEmpty() 或 list.size() 是安全的,却忘了它们的前提是对象本身不为 null。这类误判是 NPE 的高频来源。
立即学习“Java免费学习笔记(深入)”;
- 字符串判空务必先判非空再调方法:
str != null && !str.trim().isEmpty(),或用StringUtils.isNotBlank(str)(Apache Commons Lang) - 集合操作前先确认引用有效:
if (list != null && !list.isEmpty()) { ... };避免直接写list.get(0)或list.stream().findFirst() - 流式操作不免疫 NPE:
Stream.of(null).map(String::length)会在map中触发 NPE,需提前过滤:Stream.of(str).filter(Objects::nonNull).map(String::length)
public class UserService {
public User findUser(Long id) {
Objects.requireNonNull(id, "id must not be null");
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found: " + id));
}
public Optional getDisplayName(User user) {
if (user == null) return Optional.empty();
return Optional.ofNullable(user.getProfile())
.map(Profile::getName)
.filter(name -> !name.trim().isEmpty());
}
}
真正难防的不是单点 null,而是跨层传递后丢失上下文的“幽灵空值”。越靠近数据源头(DAO 层、API 入参)做拦截,后续逻辑就越干净;越依赖下游层层判空,系统就越脆弱。










