前言

如果你不想遇到烦人的NullPointerException,那么本文就是为你而生。

Java语言架构师Brian Goetz对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 提供了一个有限的机制让类库方法返回值清晰的表达有与没有值,避免很多时候 null 造成的错误。

那么,使用Optional的正确姿势是什么样的?通常,我们会使用反例来更好的学习,因此,本文通过反例来展示Optional的正确用法。

使用Optional的正确姿势

永远不要将Null赋给可选变量

使用 Optional.empty() 来初始化一个Optional,而不是直接对Optional进行赋值null。Optional相当于是一个容器/盒子,将它赋值为null是没有意义的行为。

尴尬姿势:

// 尴了个尬
public Optional<Cart> fetchCart() {
    Optional<Cart> emptyCart = null;
    ...
}

优雅姿势:

// 优雅
public Optional<Cart> fetchCart() {
    Optional<Cart> emptyCart = Optional.empty();
    ...
}

确保在调用Optional.get()之前该Optional类型对象有值

虽然使用isPresent()和get()方法组合并不是推荐的做法(事实上检查是否有值与后续对值的操作有更好的实现方式)如果你确实需要调用Optional.get()方法,那么在此之前请一定要使用Optional.isPresent()方法检查是否有值。

尴尬姿势:

// 尴了个尬
Optional<Cart> cart = ... ; // 有可能是空
...
// 如果cart为空,那么将会抛出java.util.NoSuchElementException
Cart myCart = cart.get();

优雅姿势:

// 优雅
if (cart.isPresent()) {
    Cart myCart = cart.get();
    ...
} else {
    ... // cart为空,做一些其他的事情,此处一定不能调用cart.get()
}

当没有值时,通过Optional.orElse()方法来设置/返回已经构造的默认对象

相较于使用isPresent()和get()方法组合设置/返回一个值,Optional.orElse()是一种更优雅的方案。这里有一个关键问题是,即使Optional对象是有值的,程序依然会评估orElse()方法,尽管此方法不会执行,但依然有一定的性能损失。除非orElse()方法返回的参数是已经构造的默认对象,否则建议使用下一个姿势(第四条)。

尴尬姿势:

// 尴了个尬
public static final String USER_STATUS = "UNKNOWN";
...
public String findUserStatus(long id) {
    Optional<String> status = ... ; // 可能返回空的Optional
    if (status.isPresent()) {
        return status.get();
    } else {
        return USER_STATUS;
    }
}

优雅姿势:

// 优雅
public static final String USER_STATUS = "UNKNOWN";
...
public String findUserStatus(long id) {
    Optional<String> status = ... ; // 可能返回空的Optional
    return status.orElse(USER_STATUS);
}

当没有值时,通过Optional.orElseGet()方法设置/返回不存在的默认对象

Optional.orElseGet()是替代isPresent()和get()方法组合设置/返回一个值的另一个优雅的解决方案。关键在于,orElseGet()方法的参数是Java8的Supplier。这意味着,仅当Optional容器里没有值时,Supplier方法才会执行,这样可以避免上一条orElse()方法重复创建对象和执行代码所产生的性能损耗。

尴尬姿势:

// 尴了个尬
public String computeStatus() {
    ... // 计算状态
}
public String findUserStatus(long id) {
    Optional<String> status = ... ; // 可能返回空的Optional
    if (status.isPresent()) {
        return status.get();
    } else {
        return computeStatus();
    }
}

另一种错误姿势:

/ 尴了个尬
public String computeStatus() {
    ... // 计算状态
}
public String findUserStatus(long id) {
    Optional<String> status = ... ; // 可能返回空的Optional
    // 即使"status"里有值,computeStatus()依然会被执行
    return status.orElse(computeStatus());
}

优雅姿势:

// 优雅
public String computeStatus() {
    ... // 计算状态
}
public String findUserStatus(long id) {
    Optional<String> status = ... ; // 可能返回空的Optional
    // 只有"status"里为空,computeStatus()才会被执行
    return status.orElseGet(this::computeStatus);
}

Java 10 提供orElseThrow()方法,如果没有值,该方法将会抛出java.util.NoSuchElementException异常

该方法是isPresent()和get()方法组合的另一优雅替代品,有时,当一个Optional值不存在时,您想要抛出java.util.NoSuchElementException异常,从Java 10开始,这可以通过不带参数的方法orElseThrow()完成。而对于Java8和9,请参考下一条。

尴尬姿势:

// 尴了个尬
public String findUserStatus(long id) {
    Optional<String> status = ... ; // 可能返回空的Optional
    if (status.isPresent()) {
        return status.get();
    } else {
        throw new NoSuchElementException();
    }
}

优雅姿势:

// 优雅
public String findUserStatus(long id) {
    Optional<String> status = ... ; // 可能返回空的Optional
    return status.orElseThrow();
}

当没有值时,通过orElseThrow抛出显式异常(Supplier <?extends X> exceptionSupplier)

Optional.orElseThrow(Supplier<? extends X> exceptionSupplier)也是isPresent()和get()方法组合的另一优雅替代品。有时,当一个Optional值不存在时,您要做的就是抛出一个显式异常。而从Java 10开始,java.util.NoSuchElementException异常只是依赖于orElseThrow()不带参数的方法(上一条)。

尴尬姿势:

// 尴了个尬
public String findUserStatus(long id) {
    Optional<String> status = ... ; // 可能返回空的Optional
    if (status.isPresent()) {
        return status.get();
    } else {
        throw new IllegalStateException();
    }
}

优雅姿势:

// 优雅
public String findUserStatus(long id) {
    Optional<String> status = ... ; // 可能返回空的Optional
    return status.orElseThrow(IllegalStateException::new);
}

当你有一个Optional并且需要将其赋为空引用时,使用orElse(null)

如果你想把一个Optional容器赋为空引用null,可以使用orElse(null)方法。此方法一般不推荐使用,但是确实有的时候会存在有这样需求的场景。

一个典型的使用场景是,我们要调用的方法在某种情况下接收null作为参数。例如,Java反射API中的Method.invoke()方法,此方法的第一个参数是调用此特性方法的对象示例。而如果是静态方法,第一个参数应该传null。

尴尬姿势:

// 尴了个尬
Method myMethod = ... ;
...
// Optional包含MyClass的实例,如果myMethod是静态方法,则Optional里是空。
Optional<MyClass> instanceMyClass = ... ;
...
if (instanceMyClass.isPresent()) {
    myMethod.invoke(instanceMyClass.get(), ...);
} else {
    myMethod.invoke(null, ...);
}

优雅姿势:

// 优雅
Method myMethod = ... ;
...
// Optional包含MyClass的实例,如果myMethod是静态方法,则Optional里是空。
Optional<MyClass> instanceMyClass = ... ;
...
myMethod.invoke(instanceMyClass.orElse(null), ...);

如果Optional的值存在,则执行某些操作。如果不存在则什么都不做。此时推荐使用Optional.ifPresent()

Optional.ifPresent()方法是isPresent()和get()方法组合的完美替代品。

尴尬姿势:

// 尴了个尬
Optional<String> status = ... ;
...
if (status.isPresent()) {
    System.out.println("Status: " + status.get());
}

优雅姿势:

// 优雅
Optional<String> status ... ;
...
status.ifPresent(System.out::println);

如果Optional的值存在,则执行某些操作。如果不存在则执行其他的操作。此时推荐使用Optional.ifPresentElse(),始于Java 9。

尴尬姿势:

// 尴了个尬
Optional<String> status = ... ;
if(status.isPresent()) {
    System.out.println("Status: " + status.get());
} else {
    System.out.println("Status not found");
}

优雅姿势:

// 优雅
Optional<String> status = ... ;
status.ifPresentOrElse(
    System.out::println,
    () -> System.out.println("Status not found")
)

当值存在时,设置/返回可选项。如果没有值,则设置/返回其他可选项。此时推荐使用Optional.or(),始于Java 9。

尴尬姿势:

// 尴了个尬
public Optional<String> fetchStatus() {
    Optional<String> status = ... ;
    Optional<String> defaultStatus = Optional.of("PENDING");
    if (status.isPresent()) {
        return status;
    } else {
        return defaultStatus;
    }
}

另一尴尬姿势:

// 尴了个尬
public Optional<String> fetchStatus() {
    Optional<String> status = ... ;
    return status.orElseGet(() -> Optional.<String>of("PENDING"));
}

优雅姿势:

// 优雅
public Optional<String> fetchStatus() {
    Optional<String> status = ... ;
    Optional<String> defaultStatus = Optional.of("PENDING");
    return status.or(() -> defaultStatus);
    // 或者不需要定义defaultStatus
    // return status.or(() -> Optional.of("PENDING"));
}