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的最佳实践。
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())。
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参数时,方法签名会变得更长,可读性更差。
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