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; } }