Java8(5):使用 Optional 处理 null

写过 Java 程序的同窗,通常都遇到过 NullPointerException :) —— 为了避免抛出这个异常,咱们便会写以下的代码:java

User user = getUserById(id);
if (user != null) {
    String username = user.getUsername();
    System.out.println("Username is: " + username); // 使用 username
}

可是不少时候,咱们可能会忘记写 if (user != null) —— 若是在开发阶段就发现那还好,可是若是在开发阶段没有测试到问题,等到上线却出了 NullPointerException ... 画面太美,我不敢继续想下去。数据库


为了解决这种尴尬的处境,JDK 终于在 Java8 的时候加入了 Optional 类,查看 Optional 的 javadoc 介绍:app

A container object which may or may not contain a non-null value. If a value is present, isPresent() will return true and get() will return the value.

这是一个能够包含或者不包含非 null 值的容器。若是值存在则 isPresent()方法会返回 true,调用 get() 方法会返回该对象。函数

java.util.Optional<T>

JDK 提供三个静态方法来构造一个 Optional
1.Optional.of(T value),该方法经过一个非 nullvalue 来构造一个 Optional,返回的 Optional 包含了 value 这个值。对于该方法,传入的参数必定不能为 null,不然便会抛出 NullPointerException测试

2.Optional.ofNullable(T value),该方法和 of 方法的区别在于,传入的参数能够为 null —— 可是前面 javadoc 不是说 Optional 只能包含非 null 值吗?咱们能够看看 ofNullable 方法的源码:this

Optional.ofNullable 方法

原来该方法会判断传入的参数是否为 null,若是为 null 的话,返回的就是 Optional.empty()spa

3.Optional.empty(),该方法用来构造一个空的 Optional,即该 Optional 中不包含值 —— 其实底层实现仍是 若是 Optional 中的 valuenull 则该 Optional 为不包含值的状态,而后在 API 层面将 Optional 表现的不能包含 null 值,使得 Optional 只存在 包含值不包含值 两种状态。code

Optional.empty() 的实现


前面 javadoc 也有提到,OptionalisPresent() 方法用来判断是否包含值,get() 用来获取 Optional 包含的值 —— 值得注意的是,若是值不存在,即在一个Optional.empty 上调用 get() 方法的话,将会抛出 NoSuchElementException 异常
咱们假设 getUserById 已是个客观存在的不能改变的方法,那么利用 isPresentget 两个方法,咱们如今能写出下面的代码:对象

Optional<User> user = Optional.ofNullable(getUserById(id));
if (user.isPresent()) {
    String username = user.get().getUsername();
    System.out.println("Username is: " + username); // 使用 username
}

好像看着代码是优美了点 —— 可是事实上这与以前判断 null 值的代码没有本质的区别,反而用 Optional 去封装 value,增长了代码量。因此咱们来看看 Optional 还提供了哪些方法,让咱们更好的(以正确的姿式)使用 Optionalblog

1.ifPresent

ifPresent

若是 Optional 中有值,则对该值调用 consumer.accept,不然什么也不作。
因此对于上面的例子,咱们能够修改成:

Optional<User> user = Optional.ofNullable(getUserById(id));
user.ifPresent(u -> System.out.println("Username is: " + u.getUsername()));

2.orElse

orElse

若是 Optional 中有值则将其返回,不然返回 orElse 方法传入的参数。

User user = Optional
        .ofNullable(getUserById(id))
        .orElse(new User(0, "Unknown"));
        
System.out.println("Username is: " + user.getUsername());

3.orElseGet

orElseGet

orElseGetorElse 方法的区别在于,orElseGet 方法传入的参数为一个 Supplier 接口的实现 —— 当 Optional 中有值的时候,返回值;当 Optional 中没有值的时候,返回从该 Supplier 得到的值。

User user = Optional
        .ofNullable(getUserById(id))
        .orElseGet(() -> new User(0, "Unknown"));
        
System.out.println("Username is: " + user.getUsername());

4.orElseThrow

orElseThrow

orElseThroworElse 方法的区别在于,orElseThrow 方法当 Optional 中有值的时候,返回值;没有值的时候会抛出异常,抛出的异常由传入的 exceptionSupplier 提供。

User user = Optional
        .ofNullable(getUserById(id))
        .orElseThrow(() -> new EntityNotFoundException("id 为 " + id + " 的用户没有找到"));

举一个 orElseThrow 的用途:在 SpringMVC 的控制器中,咱们能够配置统一处理各类异常。查询某个实体时,若是数据库中有对应的记录便返回该记录,不然就能够抛出 EntityNotFoundException ,处理 EntityNotFoundException 的方法中咱们就给客户端返回Http 状态码 404 和异常对应的信息 —— orElseThrow 完美的适用于这种场景。

@RequestMapping("/{id}")
public User getUser(@PathVariable Integer id) {
    Optional<User> user = userService.getUserById(id);
    return user.orElseThrow(() -> new EntityNotFoundException("id 为 " + id + " 的用户不存在"));
}

@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<String> handleException(EntityNotFoundException ex) {
    return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}

5.map

map

若是当前 OptionalOptional.empty,则依旧返回 Optional.empty;不然返回一个新的 Optional,该 Optional 包含的是:函数 mapper 在以 value 做为输入时的输出值。

String username = Optional.ofNullable(getUserById(id))
                        .map(user -> user.getUsername())
                        .orElse("Unknown")
                        .ifPresent(name -> System.out.println("Username is: " + name));

并且咱们能够屡次使用 map 操做:

Optional<String> username = Optional.ofNullable(getUserById(id))
                                .map(user -> user.getUsername())
                                .map(name -> name.toLowerCase())
                                .map(name -> name.replace('_', ' '))
                                .orElse("Unknown")
                                .ifPresent(name -> System.out.println("Username is: " + name));

6.flatMap

flatMap

flatMap 方法与 map 方法的区别在于,map 方法参数中的函数 mapper 输出的是值,而后 map 方法会使用 Optional.ofNullable 将其包装为 Optional;而 flatMap 要求参数中的函数 mapper 输出的就是 Optional

Optional<String> username = Optional.ofNullable(getUserById(id))
                                .flatMap(user -> Optional.of(user.getUsername()))
                                .flatMap(name -> Optional.of(name.toLowerCase()))
                                .orElse("Unknown")
                                .ifPresent(name -> System.out.println("Username is: " + name));

7.filter

filter

filter 方法接受一个 Predicate 来对 Optional 中包含的值进行过滤,若是包含的值知足条件,那么仍是返回这个 Optional;不然返回 Optional.empty

Optional<String> username = Optional.ofNullable(getUserById(id))
                                .filter(user -> user.getId() < 10)
                                .map(user -> user.getUsername());
                                .orElse("Unknown")
                                .ifPresent(name -> System.out.println("Username is: " + name));

有了 Optional,咱们即可以方便且优雅的在本身的代码中处理 null 值,而再也不须要一昧经过容易忘记和麻烦的 if (object != null) 来判断值不为 null。若是你的程序还在使用 Java8 以前的 JDK,能够考虑引入 Google 的 Guava 库 —— 事实上,早在 Java6 的年代,Guava 就提供了 Optional 的实现。


号外:Java9 对 Optional 的加强
即将在今年 7 月到来的 JDK9 中,在 Optional 类中添加了三个新的方法:

  • public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)

or 方法的做用是,若是一个 Optional 包含值,则返回本身;不然返回由参数 supplier 得到的 Optional

  • public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)

ifPresentOrElse 方法的用途是,若是一个 Optional 包含值,则对其包含的值调用函数 action,即 action.accept(value),这与 ifPresent 一致;与 ifPresent 方法的区别在于,ifPresentOrElse 还有第二个参数 emptyAction —— 若是 Optional 不包含值,那么 ifPresentOrElse 便会调用 emptyAction,即 emptyAction.run()

  • public Stream<T> stream()

stream 方法的做用就是将 Optional 转为一个 Stream,若是该 Optional 中包含值,那么就返回包含这个值的 Stream;不然返回一个空的 StreamStream.empty())。

举个例子,在 Java8,咱们会写下面的代码:

// 此处 getUserById 返回的是 Optional<User>
public List<User> getUsers(Collection<Integer> userIds) {
       return userIds.stream()
                .map(this::getUserById)      // 得到 Stream<Optional<User>>
                .filter(Optional::isPresent) // 去掉不包含值的 Optional,不然若是存在为空的 Optional 下面的 get 会抛出异常
                .map(Optional::get)          // 变为 Stream<User>
                .collect(Collectors.toList());
}

而有了 Optional.stream(),咱们就能够将其简化为:

public List<User> getUsers(Collection<Integer> userIds) {
    return userIds.stream()
                .map(this::getUserById)    // 得到 Stream<Optional<User>>
                .flatMap(Optional::stream) // Stream 的 flatMap 方法将多个流合成一个流,若是 Optional 为空则对应是空的 Stream,合并时会跳过
                .collect(Collectors.toList());
}
相关文章
相关标签/搜索