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.

这句话突出了三个点:

  1. Optional 是用来作为方法返回值的
  2. Optional 是为了清晰地表达返回值中没有结果的可能性
  3. 且如果直接返回 null 很可能导致调用端产生错误(尤其是NullPointerException)

Optional的机制类似于 Java 的受检异常,强迫API调用者面对没有返回值的现实。

参透Optional的设计意图才能学会正确得使用它。

以下围绕这三个点阐述Optional的最佳实践。



Optional 是用来作为方法返回值的

  1. 不要滥用 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 已经可以识别并警告

Java 8 Optional 最佳实践_Optional

所以避免

// 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
}



总结

  1. Optional 尽量只用来作为方法返回值类型
  2. 调用了返回值为Optional的方法后,一定要做空值检查
  3. 不要过度使用 Optional 避免降低代码可读性和性能
  4. 查阅并适当使用 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