stormzhai / gitblog

MIT License
0 stars 0 forks source link

Java 8 Optional 最佳实践 #17

Open stormzhai opened 1 year ago

stormzhai commented 1 year ago

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;

  1. 不要使用Optional作为Java Bean实例域的类型

即避免以下这种代码

// AVOID public class Customer { [access_modifier] [static] [final] Optional zip; [access_modifier] [static] [final] Optional telephone = Optional.empty(); ... } 因为 Optional 没有实现Serializable接口(不可序列化)

  1. 不要使用 Optional 作为类构造器参数

即避免以下这种代码

// AVOID public class Customer { private final String name; // cannot be null private final Optional postcode; // optional field, thus may be null public Customer(String name, Optional postcode) { this.name = Objects.requireNonNull(name, () -> "Name cannot be null"); this.postcode = postcode; } public Optional getPostcode() { return postcode; } ... } 可以看到这种写法只是无谓地增加了一层包装和样板代码。

  1. 不要使用 Optional 作为Java Bean Setter方法的参数

即避免以下这种代码

// AVOID @Entity public class Customer implements Serializable { private static final long serialVersionUID = 1L; ... @Column(name="customer_zip") private Optional postcode; // optional field, thus may be null public Optional getPostcode() { return postcode; } public void setPostcode(Optional 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 getPostcode() { return Optional.ofNullable(postcode); } public void setPostcode(String postcode) { this.postcode = postcode; } ... } 由于getter返回的是Optional,外部调用时就意识到里面可能是空结果,需要进行判断。

注意:对值可能为 null 的实例域的getter 才需要使用 Optional

  1. 不要使用Optional作为方法参数的类型

这一点比较有争议,但我支持不使用Optional作为方法参数的类型。

首先,当参数类型为Optional时,所有API调用者都需要给参数先包一层Optional(额外创建一个Optional实例)浪费性能 —— 一个Optional对象的大小是简单引用的4倍。

其次,当方法有多个Optional参数时,方法签名会变得更长,可读性更差。比如:

public void renderCustomer(Optional cart, Optional renderer, Optional 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) {
... }

  1. 不要在集合中使用 Optional 类

不要在 List, Set, Map 等集合中使用任何的 Optional 类作为键,值或者元素,因为没有任何意义。

对于Map的值,可以使用 getOrDefault() 或者 computeIfAbsent() 方法设置默认值

  1. 不要把容器类型(包括 List, Set, Map, 数组, Stream 甚至 Optional )包装在Optional中

即避免

// AVOID public Optional<List> fetchCartItems(long id) { Cart cart = ... ;
List items = cart.getItems(); // this may return null return Optional.ofNullable(items); } 因为容器类都有自己空值设计,如 Collections.emptyList() Collections.emptySet() Collections.emptyMap() Stream.empty() 等

// PREFER public List fetchCartItems(long id) { Cart cart = ... ;
List items = cart.getItems(); // this may return null return items == null ? Collections.emptyList() : items; }

Optional 是为了清晰地表达返回值中没有结果的可能性

  1. 不要给Optional变量赋值 null

// AVOID public Optional fetchCart() { Optional emptyCart = null; ... } 而应该用 Optional.empty() 表达空值

// PREFER public Optional fetchCart() { Optional emptyCart = Optional.empty(); ... }

  1. 确保Optional内有值才能调用 get() 方法

如果不检查Optional是否为空值就直接调用get() 方法,就让 Optional 失去了意义 —— Optional 是为了清晰地表达返回值中没有结果的可能性,强迫API调用者面对没有返回值的现实并做检查。

目前Java 8编译器并不会对这种情况报错,但是 IDE 已经可以识别并警告

所以避免

// AVOID Optional 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() }

  1. 尽量使用 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 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 status = ... ; // prone to return an empty Optional // computeStatus() is called only if "status" is empty return status.orElseGet(this::computeStatus); }

// PREFER Optional status ... ; ... status.ifPresent(System.out::println);

如果正在学习或者使用 java 9 甚至 更高版本,Optional 有一些更新的 API (如 ifPresentOrElse ),本文不讨论。

  1. 使用 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 op1 = Optional.of(product); Optional 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