不管是Java新人还是Java高工,在开发过程中都会碰到需要判断Null值以防止空指针的情况,以往的方式要么是抛异常,要么是if{}else{},这种方式往往会造成代码的阅读性和维护性变差。这篇文章将围绕在Java8中推出的Optional<T>,来讨论如何优雅的解决判空问题。
1. 为什么使用Optional
主要用处:防止空指针(NPE)、简化if...else...判断、减少代码圈复杂度
代码示例:
// 根据userId获取用户信息
User user = userDao.getUser(userId);
// 需要对user判空
if (user != null) {
// user不为null时,还需要对user的属性判断,做对应操作
if (user.getName() != null) {
System.out.println("用户姓名:" + user.getName());
}
if (user.getSex() != null) {
System.out.println("用户性别:" + user.getSex());
}
if (user.getAge() != null) {
System.out.println("用户年龄:" + user.getAge());
}
if (user.getAddress() != null) {
System.out.println("用户地址:" + user.getAddress());
}
// ...还有其他属性判断
}
可以看到在获取到user后必须要进行非空判断,不然就会报空指针异常(NPE),同样下面的输出属性值也是一样,必须经过判断才能输出,这样就会出现大量的【!= null】逻辑判断,非常不优雅。
而Optional就解决了以上的问题,让使用者只关心业务逻辑,更加优雅的解决判空问题。
2. 什么是Optional
我们可以把Optional想象成一个盒子,而里面装的就是你需要判空的对象,这样你只需要判断盒子里是不是空的,就可以判断是不是空对象,而不需要判断盒子本身,因为盒子一直存在(盒子不可能为NULL),如下图:
![](https://i-blog.csdnimg.cn/blog_migrate/92692f781e9db0412317cdf0c0fb7f21.jpeg)
Optional是Java 8引入的新特性,并在后续版本进行了优化,增添了更多新的功能,本文会标识常用方法可以使用的版本。
3. 常用方法举例
3.1 创建空的Optional:empty()
常见用法:业务不满足某条件时,返回空的Optional
public Optional<User> getManUser(Long userId) {
// ...一些业务逻辑
if (user.getSex() != Sex.MAN) {
// 如果性别是男,直接返回空
return Optional.empty();
}
}
3.2 创建非空的Optional:of()
常见用法:组装一个非空的Optional便于后续业务处理
public Optional<User> getAdultUser(Long userId) {
// ...一些业务逻辑
if (user.getAge() >= 18) {
// 如果成年了,则返回
return Optional.of(user);
}
}
3.3 创建需要判空的Optional:ofNullable()
常见用法:从数据库或接口获取一个对象后,不知道是不是空,需要进行判断
public Optional<User> getUserFromUserCenter(Long userId) {
// ...一些业务逻辑
// 从外部接口获取了User信息
User user = userService.getUser(userId);
Optional<User> userOpt = Optional.ofNullable(user);
// 或者简化直接进行包装
Optional<User> userOpt = Optional.ofNullable(userService.getUser(userId));
}
3.4 判断Optional中对象是否是NULL:isPresent()
常见用法:判空后进行业务处理
// 接3中例子
if (userOpt.isPresent()) {
// 返回true,说明user不为NULL,则对其中的user对象进行处理
} else {
// 返回false,说明user为NULL
System.out.println("用户不存在");
}
3.5 非空表达式if:ifPresent()
常见用法:代替if(xxx != null){},判断非空之后进行逻辑处理
// 接3中例子
// 如果user存在,则打印出名字
userOpt.ifPresent(user -> System.out.println(user.getName()));
3.6 非空表达式if else(JAVA9) :ifPresentOrElse()
常见用法:代替if(xxx != null){} else {},对空和非空分别进行逻辑处理
// 如果user存在,则打印出名字, 否则提示不存在,相当于4中代码的改造
userOpt.ifPresentOrElse(user -> System.out.println(user.getName()), () -> System.out.println("用户不存在"));
3.7 设置默认值:orElse()
常见用法:当对象为NULL时,需要指定一个默认值
// 如果user为NULL,则给new一个User
User user = Optional.ofNullable(user).orElse(new User());
3.8 设置默认值:orElseGet()
常见用法:当对象为NULL时,需要指定一个默认值,与orElseGet()的区别是,如果Optional中对象不为Null,则不会执行orElseGet()中的方法
// 如果user为NULL,则从getDefaultUser中获取一个User
User user = Optional.ofNullable(user).orElseGet(UserUtil::getDefaultUser);
// 以下是orElse()和orElseGet()的对比
// 如果user为null,则两者都会执行UserUtil.getDefaultUser()方法
// 如果user不为null,则orElse会执行UserUtil.getDefaultUser()方法,orElseGet不会
// 所以某些情况下orElseGet性能更好
User user = Optional.ofNullable(user).orElse(UserUtil.getDefaultUser());
User user = Optional.ofNullable(user).orElseGet(UserUtil::getDefaultUser);
3.9 过滤值:filter()
常见用法:需要对Optional中的对象进行过滤
// 判断user的年龄是否大于18岁
Optional<User> userOpt = Optional.ofNullable(user);
System.out.println(userOpt.filter(user -> user.getAge() >= 18).isPresent());
3.10 转换值:map()
常见用法:将原始的Optional对象转换为新的Optional对象
// 将Optional<User>转换为Optional<Developer>
Optional<Developer> devOpt = Optional.ofNullable(user).map(user -> {
Developer dev = net Developer();
dev.setDevName(user.getName());
return dev;
});
4. 实战改造
4.1 解决checkStyle问题
开发过程中经常会碰到这种问题,可以使用Optional简化
![](https://i-blog.csdnimg.cn/blog_migrate/988390b100bd36c93e85a8a44d6cfab9.png)
原代码:
BaseMasterSlaveServersConfig smssc = new BaseMasterSlaveServersConfig();
if (clientName != null) {
smssc.setClientName(clientName);
}
if (idleConnectionTimeout != null) {
smssc.setIdleConnectionTimeout(idleConnectionTimeout);
}
if (connectTimeout != null) {
smssc.setConnectTimeout(connectTimeout);
}
if (timeout != null) {
smssc.setTimeout(timeout);
}
if (retryAttempts != null) {
smssc.setRetryAttempts(retryAttempts);
}
if (retryInterval != null) {
smssc.setRetryInterval(retryInterval);
}
if (reconnectionTimeout != null) {
smssc.setReconnectionTimeout(reconnectionTimeout);
}
if (password != null) {
smssc.setPassword(password);
}
if (failedAttempts != null) {
smssc.setFailedAttempts(failedAttempts);
}
// ...后面还有很多这种判断,一个if就是一个分支,会增长圈复杂度
改造后:
Optional.ofNullable(clientName).ifPresent(smssc::setClientName);
Optional.ofNullable(idleConnectionTimeout).ifPresent(smssc::setIdleConnectionTimeout);
Optional.ofNullable(connectTimeout).ifPresent(smssc::setConnectTimeout);
Optional.ofNullable(timeout).ifPresent(smssc::setTimeout);
Optional.ofNullable(retryAttempts).ifPresent(smssc::setRetryAttempts);
Optional.ofNullable(retryInterval).ifPresent(smssc::setRetryInterval);
Optional.ofNullable(reconnectionTimeout).ifPresent(smssc::setReconnectionTimeout);
// ...缩减为一行,不但减少了圈复杂度,而且减少了行数
4.2 简化if...else...
原代码:
// 获取用户,找不到则新增,找到则改变用户状态
User user = userDao.getUserById(userId);
if (user == null) {
User user = new User();
user.setName("用户");
userDao.save(user);
} else {
user.setStatus(1);
userDao.update(user);
}
改造后:
// 把新增用户和更新用户的方法提出来,可读性更高
Optional.ofNullable(userDao.getUserById(userId))
.ifPresentOrElse(user -> saveUser(), user -> updateUser(user));
// 保存用户
private void saveUser(){
User user = new User();
user.setName("用户");
userDao.save(user);
}
// 更新用户
private void updateUser(User user){
user.setStatus(1);
userDao.update(user);
}
5. 本文作者及团队介绍
王翰卿,曾任快手商业化高级Java开发,现三翼鸟交付中心Java开发工程师,坚信优雅的开发方式是提升个人技术水平的的关键,业余爱好钢琴,主机游戏,来自三翼鸟数字化技术平台-交易交付能力平台团队。
交易交付能力平台团队负责搭建门店数字化转型工具,包括:海尔智家体验店小程序、三翼鸟工作台APP、商家中心等产品形态,通过数字化工具,实现门店的用户上平台、交互上平台、交易上平台、交付上平台,从而助力海尔专卖店的零售转型,并实现三翼鸟店的场景创新。