Optional 是 Java 8 引进的一个新特性,通常用于缓解常见的空指针异常问题。Brian Goetz (Java语言设计架构师)对Optional设计意图的原话如下:
Optional is intended to provide a limited mechanism for library method return types where there needed to be a clear way to represent “no result," and using null for such was overwhelmingly likely to cause errors.
这句话突出了三个点:
1、Optional 是用来作为方法返回值的
2、Optional 是为了清晰地表达返回值中没有结果的可能性
3、且如果直接返回 null 很可能导致调用端产生错误(尤其是NullPointerException)
Optional 的机制类似于 Java 的受检异常,强迫API调用者面对没有返回值的现实。参透 Optional 的设计意图才能学会正确得使用它。下面介绍一下Optional方法以及围绕这三个点阐述 Optional的最佳实践。
一、Optional的相关方法介绍
1、 ifPresent —— 如果 Optional 中有值,则对该值调用 consumer.accept,否则什么也不做。
2、orElse —— 如果 Optional 中有值则将其返回,否则返回 orElse 方法传入的参数。
3、orElseGet —— 与 orElse 方法的区别在于,orElseGet 方法传入的参数为一个 Supplier 接口的实现 —— 当 Optional 中有值的时候,返回值;当 Optional 中没有值的时候,返回从该 Supplier 获得的值。
User user = Optional.ofNullable(getUserById(id)) .orElse(new User(0, "Unknown")); User user = Optional.ofNullable(getUserById(id)) .orElseGet(() -> new User(0, "Unknown"));
4、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 作为输入时的输出值。而且我们可以多次使用map操作:
Optional<String> username = Optional .ofNullable(getUserById(id)) .map(user -> user.getUsername()) .map(name -> name.toLowerCase()) .map(name -> name.replace('_', ' '));
6、filter 方法接受一个 Predicate 来对 Optional 中包含的值进行过滤,如果包含的值满足条件,那么还是返回这个 Optional;否则返回 Optional.empty。
Optional<String> username = Optional.ofNullable(getUserById(id)) .filter(user -> user.getId() < 10) .map(user -> user.getUsername());
7、or 方法的作用是,如果一个 Optional 包含值,则返回自己;否则返回由参数 supplier 获得的 Optional
8、ifPresentOrElse 方法的用途是,如果一个 Optional 包含值,则对其包含的值调用函数 action,即 action.accept(value),这与 ifPresent 一致;与 ifPresent 方法的区别在于,ifPresentOrElse 还有第二个参数 emptyAction —— 如果 Optional 不包含值,那么 ifPresentOrElse 便会调用 emptyAction,即 emptyAction.run()
9、stream 方法的作用就是将 Optional 转为一个 Stream,如果该 Optional 中包含值,那么就返回包含这个值的 Stream;否则返回一个空的 Stream(Stream.empty())。
二、Optional 是用来作为方法返回值的
1、不要滥用 Optional API
有的同学知道了一些Optional的API后就觉得找到了一把锤子,看到什么都像钉子,于是写出了以下这种代码
String finalStatus = Optional.ofNullable(status).orElse("PENDING") // 这种写法不仅降低了代码可读性还无谓得创建了一个Optional对象(浪费性能) // 以下是同等功能但更简洁更可读的实现 String finalStatus = status == null ? "PENDING" : status;
2、不要使用Optional作为Java Bean实例域的类型,因为 Optional 没有实现 Serializable 接口(不可序列化)
3、不要使用 Optional 作为类构造器参数
4、不要使用 Optional 作为Java Bean Setter方法的参数
原因除了上面第二点提到的 Optional 是不可序列化的,还有降低了可读性。
既然 setter是用于给Java Bean 属性赋值的, 为什么还无法确定里面的值是不是空 ? 如果为空,为何不直接赋值 null (或者对应的空值) ?
但相反的是,对于可能是空值 Java Bean 属性的 Getter 方法返回值使用 Optional 类型是很好的实践。
@Entity public class Customer implements Serializable {private String postcode; // optional field, thus may be null public Optional<String> getPostcode() { return Optional.ofNullable(postcode); } public void setPostcode(String postcode) { this.postcode = postcode; } ... }
由于getter返回的是Optional,外部调用时就意识到里面可能是空结果,需要进行判断。注意:对值可能为 null 的实例域的 getter 才需要使用 Optional。
5、不要使用Optional作为方法参数的类型
首先,当参数类型为Optional时,所有API调用者都需要给参数先包一层Optional(额外创建一个Optional实例)浪费性能 —— 一个Optional对象的大小是简单引用的4倍。其次,当方法有多个Optional参数时,方法签名会变得更长,可读性更差。
三、Optional 是为了清晰地表达返回值中没有结果的可能性
1、不要给Optional变量赋值 null,而应该用 Optional.empty() 表达空值
2、确保Optional内有值才能调用 get() 方法
如果不检查Optional是否为空值就直接调用get() 方法,就让 Optional 失去了意义 —— Optional 是为了清晰地表达返回值中没有结果的可能性,强迫API调用者面对没有返回值的现实并做检查。
目前Java 8编译器并不会对这种情况报错,但是 IDE 已经可以识别并警告
// 所以避免 Optional<Cart> cart = ... ; // this is prone to be empty ... // if "cart"is empty then this code will throw a java.util.NoSuchElementException Cart myCart = cart.get(); // 而应该 if (cart.isPresent()) { Cart myCart = cart.get(); ... // do something with "myCart" } else { ... // do something that doesn't call cart.get() }
3、尽量使用 Optional 提供的快捷API 避免手写 if-else 语句
在一些场景下, Optional.orElse() Optional.orElseGet() Optional.ifPresent() 可以避免手写 if-else 语句,使代码更简洁。
public static final String USER_STATUS = "UNKNOWN"; ... public String findUserStatus(long id) { Optional<String> status = ... ; // prone to return an empty Optional return status.orElse(USER_STATUS); } public String computeStatus() { ... // some code used to compute status } public String findUserStatus(long id) { Optional<String> status = ... ; // prone to return an empty Optional // computeStatus() is called only if "status" is empty return status.orElseGet(this::computeStatus); } Optional<String> status ... ; status.ifPresent(System.out::println);
4、使用 equals 而不是 == 来比较 Optional 的值,Optional 的 equals 方法已经实现了内部值比较
总结:
(1)Optional 尽量只用来作为方法返回值类型
(2)调用了返回值为Optional的方法后,一定要做空值检查
(3)不要过度使用 Optional 避免降低代码可读性和性能
(4)查阅并适当使用 Optional API