Optional和Stream虽然都是Java8的新特性,但据我观察Optional的使用频率远低于Stream,究其原因是大家对它有误解。很多人以为Optional是用来“消除”空指针的,所以当他们发现即便使用了Optional还会抛异常时,感到非常地失望,甚至是愤怒。比如当value确实为null时,直接调用Optional#get()会抛出NoSuchElementException:
// Optional#get()底层源码,当value为null时抛出NoSuchElementException,虽然不是NPE,但也是异常
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
这实在错怪Optional了!NPE是Java语言机制的一环,单靠一个Optional类如何能够消除呢?Optional的目的不是“消除空指针”,而是优雅地做空指针“探测”。就好比给了你一个排雷工具,但你就是不按正确方法使用它,最终被炸死了,这能怪谁呢?地雷是客观存在的,不可消除。你能做的就是好好利用排雷工具,避免地雷引爆。
再说回上面的Optional#get(),很多人觉得:妈的,好不容易Optional包装了null,结果又提供了一个可能抛异常的get方法,意义何在?实际上NPE之所以让人讨厌,不仅仅因为它是一个异常(我们日常开发遇到的异常还少吗),而是因为NPE往往会掩盖确切的错误信息。举个例子:
public void method1() {
User user = userService.getById(1L);
this.method2(user, 999);
}
public void method2(User user, Integer point) {
// 省略10+代码
updatePoint(user.getId(), point);
}
抛异常的是第8行的updatePoint()方法,而实际上“错误源头”是第2行的user,这会给我们排查问题造成干扰,特别是实际项目中往往调用链路更加复杂。如果使用Optional#get(),那么在get获取user的时候就会直接报错,排查问题会简单很多!
我个人基本不用Optional#get(),更习惯用orElse或orElseThrow()处理
推荐使用场景
第一个场景就是简化空指针探测,比如:
public static String getDepartmentNameOfUser(String username) {
ResultTO<User> resultTO = getUserByName(username);
if (resultTO != null) {
User user = resultTO.getData();
if (user != null) {
Department department = user.getDepartment();
if (department != null) {
return department.getName();
}
}
}
return "未知部门";
}
解决办法就是3个步骤:
- 包装value:Optional.ofNullable()
- 逐层安全地拆解value:map()
- 最终返回:orElse()/orElseGet()/orElseThrow
public static String getDepartmentNameOfUser(String username) {
return Optional.ofNullable(getUserByName(username))
.map(ResultTO::getData)
.map(User::getDepartment)
.map(Department::getName)
.orElse("未知部门");
}
其他的还可以是:
public boolean sendMessage(Long fromId, Long toId, String message) {
// 用户校验:如果用户不存在,直接抛异常
User user = Optional.ofNullable(userService.getUserById(fromId))
.orElseThrow(() -> new BizException(ErrorEnumCode.USER_NOT_EXIST));
// 组装数据并发送...
}
public List<String> listSubCities(String provinceCode) {
// 查到就返回,查不到就返回替代值(对于集合而言,尽量返回空集合)
return Optional.ofNullable(getCitiesByPid(provinceCode)).orElse(new ArrayList<String>());
}
另外,如果你需要对返回值进行判断,比如结果是否大于某个值等,可以使用Optional的filter方法:
public class OptionalFilterTest {
public static void main(String[] args) {
// 需求:调用getUser()得到person,并且person的age大于18才返回username,否则返回不存在
// 普通的写法(如果层级深一点会很难看)
Person user = getUser();
if (user != null && user.getAge() > 18) {
System.out.println(user.getName());
} else {
System.out.println("不存在");
}
// 你尝试用map(),但你发现直接返回username了,你甚至无法再次判断是否age>18
String username1 = Optional.ofNullable(getUser())
.map(Person::getName)
.orElse("不存在");
System.out.println("username1 = " + username1);
// 引入filter()
String username2 = Optional.ofNullable(getUser())
.filter(person -> person.getAge() > 18)
.map(Person::getName)
.orElse("不存在");
System.out.println("username2 = " + username2);
}
public static Person getUser() {
if (RandomUtils.nextBoolean()) {
return null;
} else {
Person person = new Person();
person.setName("鲍勃");
// commons.lang3
person.setAge(RandomUtils.nextInt(0, 50));
return person;
}
}
@Data
static class Person {
private String name;
private Integer age;
}
}