写过 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 returntrue
andget()
will return the value.
这是一个能够包含或者不包含非 null
值的容器。若是值存在则 isPresent()
方法会返回 true
,调用 get()
方法会返回该对象。函数
JDK 提供三个静态方法来构造一个 Optional
:
1.Optional.of(T value)
,该方法经过一个非 null
的 value 来构造一个 Optional
,返回的 Optional
包含了 value 这个值。对于该方法,传入的参数必定不能为 null
,不然便会抛出 NullPointerException
。测试
2.Optional.ofNullable(T value)
,该方法和 of
方法的区别在于,传入的参数能够为 null
—— 可是前面 javadoc 不是说 Optional
只能包含非 null
值吗?咱们能够看看 ofNullable
方法的源码:this
原来该方法会判断传入的参数是否为 null
,若是为 null
的话,返回的就是 Optional.empty()
。spa
3.Optional.empty()
,该方法用来构造一个空的 Optional
,即该 Optional
中不包含值 —— 其实底层实现仍是 若是 Optional
中的 value 为 null
则该 Optional
为不包含值的状态,而后在 API 层面将 Optional
表现的不能包含 null
值,使得 Optional
只存在 包含值 和 不包含值 两种状态。code
前面 javadoc 也有提到,Optional
的 isPresent()
方法用来判断是否包含值,get()
用来获取 Optional
包含的值 —— 值得注意的是,若是值不存在,即在一个Optional.empty
上调用 get()
方法的话,将会抛出 NoSuchElementException
异常。
咱们假设 getUserById
已是个客观存在的不能改变的方法,那么利用 isPresent
和 get
两个方法,咱们如今能写出下面的代码:对象
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
还提供了哪些方法,让咱们更好的(以正确的姿式)使用 Optional
。blog
1.ifPresent
若是 Optional
中有值,则对该值调用 consumer.accept
,不然什么也不作。
因此对于上面的例子,咱们能够修改成:
Optional<User> user = Optional.ofNullable(getUserById(id)); user.ifPresent(u -> System.out.println("Username is: " + u.getUsername()));
2.orElse
若是 Optional
中有值则将其返回,不然返回 orElse
方法传入的参数。
User user = Optional .ofNullable(getUserById(id)) .orElse(new User(0, "Unknown")); System.out.println("Username is: " + user.getUsername());
3.orElseGet
orElseGet
与 orElse
方法的区别在于,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
与 orElse
方法的区别在于,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
若是当前 Optional
为 Optional.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
方法与 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
方法接受一个 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
;不然返回一个空的 Stream
(Stream.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()); }