Optional 是 Java 8 引进的一个新特性,我们通常认为Optional是用于缓解Java臭名昭著的空指针异常问题。
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.
这句话突出了三个点:
- Optional 是用来作为方法返回值的
- Optional 是为了清晰地表达返回值中没有结果的可能性
- 且如果直接返回 null 很可能导致调用端产生错误(尤其是NullPointerException)
Optional的机制类似于 Java 的受检异常,强迫API调用者面对没有返回值的现实。
参透Optional的设计意图才能学会正确得使用它。
以下围绕这三个点阐述Optional的最佳实践。
Optional 是用来作为方法返回值的
- 不要滥用 Optional API
有的同学知道了一些Optional的API后就觉得找到了一把锤子,看到什么都像钉子。
于是写出了以下这种代码
String finalStatus = Optional.ofNullable(status).orElse("PENDING")
这种写法不仅降低了代码可读性还无谓得创建了一个Optional对象(浪费性能)
以下是同等功能但更简洁更可读的实现
String finalStatus = status == null ? "PENDING" : status;
2. 不要使用Optional作为Java Bean实例域的类型
即避免以下这种代码
// AVOID public class Customer { [access_modifier] [static] [final] Optional<String> zip; [access_modifier] [static] [final] Optional<String> telephone = Optional.empty(); ... }
因为 Optional 没有实现Serializable接口(不可序列化)
3. 不要使用 Optional 作为类构造器参数
即避免以下这种代码
// AVOID public class Customer { private final String name; // cannot be null private final Optional<String> postcode; // optional field, thus may be null public Customer(String name, Optional<String> postcode) { this.name = Objects.requireNonNull(name, () -> "Name cannot be null"); this.postcode = postcode; } public Optional<String> getPostcode() { return postcode; } ... }
可以看到这种写法只是无谓地增加了一层包装和样板代码。
4. 不要使用 Optional 作为Java Bean Setter方法的参数
即避免以下这种代码
// AVOID @Entity public class Customer implements Serializable { private static final long serialVersionUID = 1L; ... @Column(name="customer_zip") private Optional<String> postcode; // optional field, thus may be null public Optional<String> getPostcode() { return postcode; } public void setPostcode(Optional<String> postcode) { this.postcode = postcode; } ... }
原因除了上面第二点提到的 Optional 是不可序列化的,还有降低了可读性。
既然 setter是用于给Java Bean 属性赋值的, 为什么还无法确定里面的值是不是空 ? 如果为空,为何不直接赋值 null (或者对应的空值) ?
但相反的是,对于可能是空值Java Bean 属性的Getter 方法返回值使用Optional类型是很好的实践
// PREFER @Entity public class Customer implements Serializable { private static final long serialVersionUID = 1L; ... @Column(name="customer_zip") 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作为方法参数的类型。
首先,当参数类型为Optional时,所有API调用者都需要给参数先包一层Optional(额外创建一个Optional实例)浪费性能 —— 一个Optional对象的大小是简单引用的4倍。
其次,当方法有多个Optional参数时,方法签名会变得更长,可读性更差。比如:
public void renderCustomer(Optional<Cart> cart, Optional<Renderer> renderer, Optional<String> name) { ... }
最后,Optional参数给方法实现增加了更多检查负担 —— 以上面 renderCustomer 方法为例
cart 参数实际上有三种可能性:
1) . cart 为 null
2). cart 为 Optional.empty()
3). cart 为 Optional.of(xxx)
如果只是为了设计可选的方法参数,方法重载是个传统的且实用的方案,而且对调用者更友好
public void renderCustomer(Cart cart, Renderer renderer, String name) { ... } public void renderCustomer(Cart cart, Renderer renderer) { ... } public void renderCustomer(Cart cart) { ... }
6. 不要在集合中使用 Optional 类
不要在 List, Set, Map 等集合中使用任何的 Optional 类作为键,值或者元素,因为没有任何意义。
对于Map的值,可以使用 getOrDefault() 或者 computeIfAbsent() 方法设置默认值
7. 不要把容器类型(包括 List, Set, Map, 数组, Stream 甚至 Optional )包装在Optional中
即避免
// AVOID public Optional<List<String>> fetchCartItems(long id) { Cart cart = ... ; List<String> items = cart.getItems(); // this may return null return Optional.ofNullable(items); }
因为容器类都有自己空值设计,如 Collections.emptyList() Collections.emptySet() Collections.emptyMap() Stream.empty() 等
// PREFER public List<String> fetchCartItems(long id) { Cart cart = ... ; List<String> items = cart.getItems(); // this may return null return items == null ? Collections.emptyList() : items; }
Optional 是为了清晰地表达返回值中没有结果的可能性
8. 不要给Optional变量赋值 null
// AVOID public Optional<Cart> fetchCart() { Optional<Cart> emptyCart = null; ... }
而应该用 Optional.empty() 表达空值
// PREFER public Optional<Cart> fetchCart() { Optional<Cart> emptyCart = Optional.empty(); ... }
9. 确保Optional内有值才能调用 get() 方法
如果不检查Optional是否为空值就直接调用get() 方法,就让 Optional 失去了意义 —— Optional 是为了清晰地表达返回值中没有结果的可能性,强迫API调用者面对没有返回值的现实并做检查。
目前Java 8编译器并不会对这种情况报错,但是 IDE 已经可以识别并警告
所以避免
// AVOID 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();
而应该
// PREFER if (cart.isPresent()) { Cart myCart = cart.get(); ... // do something with "myCart" } else { ... // do something that doesn't call cart.get() }
10. 尽量使用 Optional 提供的快捷API 避免手写 if-else 语句
在一些场景下, Optional.orElse() Optional.orElseGet() Optional.ifPresent() 可以避免手写 if-else 语句,使代码更简洁
具体使用方法可以查官方API
简单示例:
// PREFER 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); } // PREFER 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); } // PREFER Optional<String> status ... ; ... status.ifPresent(System.out::println);
如果正在学习或者使用 java 9 甚至 更高版本,Optional 有一些更新的 API (如 ifPresentOrElse ),本文不讨论。
11. 使用 equals 而不是 == 来比较 Optional 的值
Optional 的 equals 方法已经实现了内部值比较
@Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof Optional)) { return false; } Optional<?> other = (Optional<?>) obj; return Objects.equals(value, other.value); }
所以
Product product = new Product(); Optional<Product> op1 = Optional.of(product); Optional<Product> op2 = Optional.of(product); if (op1.equals(op2)) { ... //expected true } if (op1 == op2){ ... //expected false }
总结
- Optional 尽量只用来作为方法返回值类型
- 调用了返回值为Optional的方法后,一定要做空值检查
- 不要过度使用 Optional 避免降低代码可读性和性能
- 查阅并适当使用 Optional API
参考资料:
https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html
https://dzone.com/articles/using-optional-correctly-is-not-optional
《Effective Java 第三版》第 55 条: 谨慎返回 Optional