Java|使用 Optional 更好地处理 null 返回值

#Java [字体 ··]

Optional 是一个容器类,是 JDK 8 提供的一个防止引起空指针异常的工具类,可以更好地封装处理返回值。

为什么更推荐使用 Optional 类封装可能为 null 的返回值?在项目开发过程中,没人绝对清楚调用方法的返回值一定存在,开发者也只是尽力保证返回值不为 null,比如查询用户列表没用户时我们就返回长度为 0 的 ArrayList,有种对任何值都不信任的编程方式,但这种方式会形成许多冗余代码,让开发者也很累,不这样做会带来讨厌的 Null Pointer Exception(NPE)问题;其次是在多层次取成员变量时,程序员能判断到吐。Optional 的出现很好的改观了这个问题,一两行代码就能代替繁琐的判空。

本文对 Optional 工具的使用技巧进行总结。

热身

开始前你可以先熟悉一下 Optional 的 API,前往菜鸟教程

坏例子

例子类:

1class User {
2  Long id;
3  String name;
4  Address addr;
5}

一个查询 User 的方法:

1  // 查询方法
2  User findUserById(Long id);

坏例子 1:

1  void method1(Long id) {
2    User u = dao.findUserById(id);
3
4    // 此时开发人员可能使用 User 类对象 u 疏忽了判空
5    u.getName(); // 用户未查到,u 为 null,发生 NPE
6  }

坏例子 2:

 1  void method2(Long id) {
 2    User u = dao.findUserById(id);
 3
 4    // 现在要取成员变量 Address 的城市。
 5    String city = null;
 6    if (u != null ) { // 判空
 7      String name = u.getName();
 8      Address addr = u.getAddress();
 9      // 这里还要判空,如果不判空可能发生 NPE
10      if (addr != null) {
11        prov = addr.getCity();
12      }
13    }
14    // 使用
15    System.out.println(city);
16  }

相信看完这个例子你已经厌恶空指针错误了,在 JDK 1.8 之前,我们通常没有好的解决办法。

这种方式首先在 Guava 上出现,JDK 1.8 吸取了这个良好的设计,接下来我们看下使用 Optional 怎么样避免上面的问题。

改良后的查询方法:

1  // 查询方法
2  Optional<User> findUserById(Long id) {
3    if (id == null) {
4      return Optional.empty();
5    }
6
7    User user = dbUtils.query(id);
8    return Optional.ofNullable(user);
9  }

对例子 1 NPE 调用改造:

1  void method1(Long id) {
2    Optional<User> u = dao.findUserById(id);
3    // before
4    // u.getName(); // 用户未查到,u 为 null,发生 NPE
5
6    // after
7    // 我们拿到了 name,它可能存在,也或者为已经猜测到的 null,中间不存在任何发生 NPE 的风险,
8    String name = u.map(User::getName).orElse(null);
9  }

当我们刻意使用 Optional 包装返回值时,同时也是潜意识的提醒用户:现在返回的这个值,可能为 null。

对例子 2 调用改造:

 1  void method2(Long id) {
 2    Optional<User> u = dao.findUserById(id);
 3
 4    // before
 5    // if (u != null ) { // 判空
 6    //   String name = u.getName();
 7    //   Address addr = u.getAddress();
 8    //   // 这里还要判空,如果不判空可能发生 NPE
 9    //   if (addr != null) {
10    //     prov = addr.getCity();
11    //   }
12    // }
13
14    // after
15    // 现在要取成员变量 Address 的城市。
16    String city = u.map(User::getAddr).map(Address::getCity).orElse("CITY EMPTY");
17
18    // 使用非常明确的对象
19    System.out.println(city);
20  }

Optional 只用一行链式调用就帮我们结束了多层嵌套的判空。

到这里相信你已经理解 Optional 的作用了,它最常用的方式就是上面的例子。

从源码看使用

1、注意点: Optional.of(obj) 只能接收非空 obj 对象,若要传入可能为 null 的对象就使用 Optional.ofNullable(obj)

 1  // 源码
 2
 3  // of 方法
 4  public static <T> Optional<T> of(T value) {
 5      return new Optional<>(value);
 6  }
 7
 8  // of方法用的构造器
 9  private Optional(T value) {
10    // 判空,value 为 null 抛异常
11    this.value = Objects.requireNonNull(value);
12  }

2、注意点: get() 不能直接使用。

1  public T get() {
2      // 判空抛异常!
3      if (value == null) {
4          throw new NoSuchElementException("No value present");
5      }
6      return value;
7  }

3、flatMapmap 的区别: flatMap必须传入 Optional 对象,而 map 不用。

使用对比:

1  Optional<User> u = dao.findUserById(id);
2  // 获取 addr 的 city
3
4  // flatMap
5  String city = u.flatMap(user -> Optional.ofNullable(user::getAddr)).flatMap(addr -> Address::getCity).orElse(null);
6
7  // map
8  String city = u.map(User::getAddr).map(Address::getCity).orElse(null);

以下为二者的源码。

flatMap

1  public<U> Optional<U> flatMap(Function<? super T, Optional<U>/** 这里限定了Optiona对象 **/> mapper) {
2      Objects.requireNonNull(mapper);
3      if (!isPresent())
4          return empty();
5      else {
6          return Objects.requireNonNull(mapper.apply(value));
7      }
8  }

map

1  public<U> Optional<U> map(Function<? super T, ? extends U/** 这里没有限定 **/> mapper) {
2      Objects.requireNonNull(mapper);
3      if (!isPresent())
4          return empty();
5      else {
6          return Optional.ofNullable(mapper.apply(value));
7      }
8  }

错误的使用方式

回到原始:

1  Optional<User> u = dao.findUserById(id);
2
3  String name = null;
4  if (u.isPresent()) {
5    User user = u.get();
6    name = user.getName();
7  }
8
9  // 使用 name

这种方式多此一举,简单取成员变量没有必要用 Optional。

1  User u = dao.findUserById(id);
2
3  Optional<User> userOpt = Optional.ofNullable(u);
4  String name = userOpt.map(User::getName).orElse(null);
5
6  // 传统方式一行解决
7  String name = u != null ? u.getName() : null;
8
9  // 使用 name

请记住不要使用这些方式让开发变复杂。

总结

小结一下。

1、Optional 通常只使用在返回值可能为 null 的方法作为返回值。

2、Optional 非常适合取一个对象的多层嵌套成员变量。

3、注意 of 的入参不为 null,这是个炸弹。

4、注意使用 get 前先判断是否为空,这也是个炸弹。

参考:


博客没有评论系统,可以通过 邮件 评论和交流。 Top↑