外观
【Optional】空指针银弹
约 791 字大约 3 分钟
2025-10-18
线上故障常客NPE的三大困境
在日常开发中,最常见的bug就是NPE,取值一时爽,判空非常头痛,要么是忘了,要么是嵌套取值,层层判空,导致代码很难看,一起分析下研发过程中判空最头痛的几个问题。
- 取值模糊
顾名思义,就是取值的人,不知道这个值是否可能为空,没经验的人,直接取值,有经验的,会探索值的来源,判断是否可能为空,再加上判断,但是这个非常依赖经验,且不可靠,这也是大多数程序员容易忽略的地方。 比如 User getUserById(Long id);这段代码,从返回的对象User中,是无法看出这个值可能为空的,要显示的感知,可以使用这段代码,Optional<User> getUserById(Long id);。 根据返回签名中的Optional就知道这个值有可能是空值。
- 层层嵌套
if (outerObject != null) {
MiddleObject middleObject = outerObject.getMiddleObject();
if (middleObject != null) {
InnerObject innerObject = middleObject.getInnerObject();
if (innerObject != null) {
return innerObject.getProperty();
}
}
}
return null;这段代码不仅冗长,且可读性极差,谁看谁头晕,为了解决这个问题,java8引入银弹Optional。 上面这段代码,如果使用Optional,如何实现?
return Optional.ofNullable(outerObject).flatMap(outer -> outer.getMiddleObject())
.flatMap(midle -> midle.getInnerObject()).flatMap(inner -> inner.getProperty()).orElse(null);通过上面实现,看着舒服多了,简洁易懂。
- 空值传播
上游没做判空处理,导致下游NPE,容易给下游使用者埋坑。
// 上游方法
public User getUser() {
// 某些情况下可能返回null
return null;
}
// 下游方法
public String getUserName() {
User user = getUser();
return user.getName(); // 这里会抛出NullPointerException
}Optional 核心API
创建的三种方式
Optional.of(T value)
要求value值非空,否则立即抛出异常NullPointerException
Optional.ofNullable(T value)
允许value为空,若为null则返回空Optional,也是日常开发中是最常用的创建方式。
Optional.empty()
显示空值,创建一个无值的Optional,用于特定业务逻辑中,需要明确返回一个空值的情况。比如在一个查询方法中,如果没有找到对应的数据,就返回Optional.empty()。
取值策略
ifPresent(Consumer<T> action)
先判断,再消费。
Optional<User> optionalUser = Optional.ofNullable(user);
optionalUser.ifPresent(u -> System.out.println("User name: " + u.getName()));orElse(T default) vs orElseGet(Supplier<T> supplier)
如果值不存在,就返回default默认值。
String nickName = optionalUser.orElse(new User()).getNickName();如果默认值,还需要进行操作,才能返回,可以通过函数式接口Supplier<T>传入一个可以返回结果的表达式。
String nickName = optionalUser.orElseGet(() -> userService.getDefaultUser()).getNickName();结合场景,避免滥用
- 修饰方法返回值
方法返回值使用Optional,告诉方法使用者,这个值可能为空,NPE的锅我不背,判不判空,你随意。
public Optional<User> findUserById(Long id) {
User user = userRepository.findById(id);
return Optional.ofNullable(user);
}- 链式对象访问
访问层层嵌套对象属性
Optional<User> optionalUser = Optional.ofNullable(user);
String city = optionalUser
.flatMap(User::getAddress)
.flatMap(Address::getCity)
.orElse("Unknown");- 集合判空
List<User> userList = userService.findUsers();
List<String> emailList = Optional.ofNullable(userList)
.flatMap(users -> users.stream()
.flatMap(User::getEmail)
.collect(Collectors.toList()))
.orElse(Collections.emptyList());让我们告别「Null 恐惧症」,用 Optional 重构空值处理的最佳实践。