Optional的使用和介绍

Optional对象是一种包装器对象,要么包装了类型T的对象,要么没有保证任何对象。————Java核心技术

也就是说,Optional有以下几个用处:

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

首先看源码,很明显,value存储值,类似一个容器

public final class Optional<T> {
    /**
     * Common instance for {@code empty()}.
     */
    private static final Optional<?> EMPTY = new Optional<>(null);

    /**
     * If non-null, the value; if null, indicates no value is present
     */
    private final T value;

方法介绍

基本方法

基本方法描述
empty()创建一个空的 Optional 实例。
of(T value)创建一个包含非空值的 Optional 实例。如果传入的值为 null,则抛出 NullPointerException
ofNullable(T value)创建一个 Optional 实例,如果传入的值为 null,则创建一个空的 Optional 实例。
查询方法描述
isPresent()如果存在非空值,则返回 true,否则返回 false
isEmpty()Java 11 引入,如果值不存在(即为 null),则返回 true,否则返回 false
条件动作方法描述
ifPresent(Consumer)如果值存在,执行给定的操作。
ifPresentOrElse(Consumer, Runnable)Java 9 引入,如果值存在,执行给定操作;否则执行另一操作。
获取值方法描述
get()如果值存在,返回值;否则抛出 NoSuchElementException
orElse(T other)如果有值则将其返回,否则返回指定的其它值。
orElseGet(Supplier)如果有值则将其返回,否则返回从指定的 Supplier 获取的值。
orElseThrow(Supplier)如果有值则将其返回,否则抛出 Supplier 提供的异常。Java 10 引入的简化形式不需要 Supplier 参数。
转换方法描述
map(Function)如果有值,则应用提供的映射函数,如果结果不为 null,则返回一个包含映射结果的 Optional
flatMap(Function)如果有值,应用映射函数给值,映射函数必须返回 Optional
filter(Predicate)如果有值并且值匹配给定的谓词,则返回包含该值的 Optional;否则返回一个空的 Optional

基本方法都很好理解,使用一个最简单的demo

  Cat cat = null;
        Cat cat1 = new Cat(12,"aaa") ;
        Cat cat2 = new Cat(12,"bbb") ;
        cat2.setName(null);
        Optional<Cat> optionalCat = Optional.ofNullable(cat);
        Optional<Cat> optionalCat1 = Optional.of(cat1);
        Optional<Cat> optionalCat2 = Optional.of(cat2);

        System.out.println(optionalCat==null);
        System.out.println(optionalCat);
        System.out.println(optionalCat1);
        System.out.println(optionalCat2);

输出:

false
Optional.empty
Optional[Cat{age=12, name='aaa'}]
Optional[Cat{age=12, name='null'}]

ifPresentOrElse(Consumer, Runnable)

这个方法怎么解释呢?先看管方介绍

如果存在值,则使用该值执行给定的操作,否则执行给定的基于空的操作。形参: action–如果存在值,则执行的操作emptyAction–如果不存在值,将执行基于空的操作抛出: NullPointerException–如果存在一个值并且给定的操作为null,或者不存在任何值并且给定基于空的操作为null。自: 9

养成阅读源码的习惯:

 public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
        if (value != null) {
            action.accept(value);
        } else {
            emptyAction.run();
        }
    }

查看Consumer,发现是函数式接口,也就是说这里只需要写出操作即可

    public static void main(String[] args) {

        // 创建一个包含字符串的Optional对象
        Optional<String> optionalWithValue1 = Optional.of("Hello, World!");
        Optional<String> optionalWithValue2 = Optional.of("Hello, World!");
        
        optionalWithValue2 = Optional.empty();
        optionalWithValue1.ifPresentOrElse(value-> System.out.println("Optional contains: " + value),()-> System.out.println("Optional is empty"));
        optionalWithValue2.ifPresentOrElse(value-> System.out.println("Optional contains: " + value),()-> System.out.println("Optional is empty"));
    }

orElseThrow(Supplier)同理,当调用Optional.orElseThrow().someMethod()时,该方法会抛出异常,这里不做演示,这个命名是为了让程序员知道这种情况不会发生时,才会使用该方法

这里展示最后三种方法的运用

public class OptionalDemo {
    public static void main(String[] args) {
        // 示例字符串
        String text = "hello";

        // 使用 map 方法转换字符串为大写
        Optional<String> upperText = Optional.ofNullable(text).map(String::toUpperCase);
        System.out.println("Using map to upper case: " + upperText.orElse("No value found"));

        // 使用 flatMap 方法进行转换。这里模拟返回一个Optional包装的结果
        Function<String, Optional<String>> addExclamation = s -> Optional.of(s + "!");
        Optional<String> excitedText = Optional.ofNullable(text).flatMap(addExclamation);
        System.out.println("Using flatMap to add exclamation: " + excitedText.orElse("No value found"));

        // 使用 filter 方法过滤长度大于 3 的字符串
        Predicate<String> lengthGreaterThan3 = s -> s.length() > 3;
        Optional<String> filteredText = Optional.ofNullable(text).filter(lengthGreaterThan3);
        System.out.println("Using filter to find strings longer than 3 characters: " + filteredText.orElse("No value found or doesn't meet criteria"));
    }
}

应用场景

有的同学知道了一些Optional的API后就觉得找到了一把锤子,看到什么都像钉子。

  1. getter方法使用
// 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;
    }
    ...
}
  1. 使用API减少if else
// 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);

还有许多使用事项可以参考这篇文章,最后注意不要滥用API哦,因为当参数类型为Optional时,所有API调用者都需要给参数先包一层Optional(额外创建一个Optional实例)浪费性能 —— 一个Optional对象的大小是简单引用的4倍。

这里列举一些不适合使用的场景

下面是一些有关 0ptional类型正确用法的提示:

  1. 0ptional类型的变量永远都不应该为null。
  2. 不要使用 0ptional类型的域。因为其代价是额外多出来一个对象。在类的内部,应该使用null表示缺失的域更易于操作。如果不希望使用0ptional类型的域,那么就让类是不可序列化的。
  3. 类型为 0ptional的方法参数是有问题的。在很多常见的要求值必须存在的情况下,对它们的调用会令人不快。应该考虑编写该方法的两个重载版本,分别是包含和不包含该参数的版本。(另外,返回0otional对象是没问题的,因为它是一种表示该函数可能没有结果的恰当方式。)
  4. 不要在集合中放置 0ptional 对象,并且不要将它们用作 map 的键。应该直接收集其中的值

第三点意思是:

不推荐!!!

import java.util.Optional;

public class User {
    private Optional<String> name = Optional.empty();

    public Optional<String> getName() {
        return name;
    }

    public void setName(String name) {
        this.name = Optional.ofNullable(name);
    }
}

其他使用注意可以参考这篇Java 8 Optional 最佳实践 - 知乎 (zhihu.com)