03 Optional

概述

在从数据库查询数据或者执行一些其他操作的时候,查询出来的结果可能是为空的,返回的是 null,如果不对返回值进行判断,直接对 null 进行操作,则会报空指针异常。

传统的方式是使用 if 条件判断来判断对象是否为空,并执行相应的处理逻辑:

if (authors != null) {
    System.out.println(author.getName());
}

如果存在大量的非空判断,代码就会显得十分臃肿,可读性也会降低。因此 Java 8 引入了 Optional 类,用于处理可能为空的值的容器类。它提供了一种优雅的方式来处理可能存在或不存在的值,避免了空指针异常的发生

官方文档如下:

https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html

Class Optional< T>

This method supports post-processing on optional values, without the need to explicitly check for a return status. For example, the following code traverses a stream of file names, selects one that ha

以下是 Optional 类的一些重要特点和用法:

  • 容器类:Optional 是一个容器类,可以包含一个非空的值或者为空。它通过 of()、ofNullable() 和 empty() 等静态方法来创建 Optional 对象。
  • 避免空指针异常:Optional 提供了一种安全的方式来处理可能为空的值。通过使用 Optional,我们可以避免显式地进行空值检查,从而减少了空指针异常的风险
  • 方法链操作:Optional 提供了一系列方法来对包含的值进行操作,如 map()、flatMap()、filter() 等。这些方法可以在值存在的情况下进行操作,并返回一个新的 Optional 对象。
  • isPresent() ifPresent():Optional 提供了 isPresent() 方法来检查值是否存在,以及 ifPresent() 方法来在值存在时执行特定的操作。
  • 默认值:Optional 提供了 orElse()、orElseGet() 和 orElseThrow() 等方法来获取值或者提供默认值,以应对值为空的情况。
  • 空值处理:Optional 提供了 ifPresentOrElse() 方法来在值存在或者为空时执行不同的操作。
  • 使用 Optional 类可以使代码更加健壮和可读,同时提供了一种优雅的方式来处理可能为空的值。

使用

1. 创建对象

Optional 就好像是包装类,可以把我们的具体数据封装到 Optional 对象内部。然后去使用 Optional 中封装好的方法操作封装进去的数据就可以避免空指针异常。

ofNullable

一般使用 Optional 的静态方法 ofNullable(ofNullable(T value))来把数据封装成一个 Optional 对象。如果传入的参数为null,将会返回一个空的 Optional 对象。

public static void main(String[] args) {
    Author author = getAuthor();
    Optional<Author> optionalAuthor = Optional.ofNullable(author);
    optionalAuthor.ifPresent(System.out::println);
}

这样处理感觉和使用 if 做判空好像差不多,在获取数据后都要加一行代码来处理。但是如果改造下 getAuthor 方法,让其的返回值就是封装好的 Optional 的话,我们在使用时就会方便很多。

而且在实际开发中,数据很多是从数据库获取的,MyBatis3.5 版本开始也已经支持 Optional 了,可以直接把 Dao 层方法的返回值类型定义成 Optional 类型,封装的过程也不需要自己操作,MyBatis 会自己把数据封装成 Optional 对象返回。

of

of(T value):of 方法只能处理非空的对象。如果确定一个对象不是空的,则可以使用 of 方法来把数据封装成 Optional 对象。

empty

empty():返回一个空的 Optional 对象。如果明确知道某个对象为 null,那么可以直接使用 empty() 封装。

ofNullable() 本质也是调用 of() 和 empty() 实现的:

public static <T> Optional<T> ofNullable(T value) {
 return value == null ? empty() : of(value);
}

2. 安全消费值

ifPresent(Consumer<? super T> consumer):如果 Optional 对象中的对象不为 null,那么就会调用 consumer 中定义的逻辑处理,如果为 null,则不执行任何操作。

获取到一个 Optional 之后,可以使用其 ifPresent() 方法来消费其中的值。这个方法会判断其内封装的数据是否为空,不为空时才会执行具体的消费代码,这样使用起来就更加安全了。

例如,以下写法就避免了空指针异常。

public static void main(String[] args) {
    Author author = getAuthor();
    Optional<Author> optionalAuthor = Optional.ofNullable(author);
    optionalAuthor.ifPresent(System.out::println);
}

3. 获取值

get():如果 Optional 中的值不为 null,则返回该对象,如果为空,则会抛 NoSuchElementException 异常。

如果想获取值自己进行处理,可以使用 get() 方法获取,但是因为空的 Optional 调用 get() 会抛异常,因此还需要使用 isPresent() 方法判断 Optional 中值是否存在,如果存在返回 true,否则返回 false。

public static void main(String[] args) {
    Author author = getAuthor();
    Optional<Author> optionalAuthor = Optional.of(author);
    if (optionalAuthor.isPresent()) {
        Author author2 = optionalAuthor.get();
        System.out.println(author2);
    }
}

但是这种方式和直接对对象进行判空差不多,因此,建议直接使用 ifPresent 方法。

4. 安全获取值

直接使用 get() 方法获取值还是可能存在异常问题,因此,如果期望安全地获取值,可以使用 Optional 的下列方法。

orElseGet

orElseGet(Supplier<? extends T> other):如果存在则返回值,否则返回设置好的默认值。

例如:

public static void main(String[] args) {
    Author author = getAuthor();
    Optional<Author> optionalAuthor = Optional.of(author);
    Author author2 = optionalAuthor.orElseGet(new Supplier<Author>() {
        @Override
        public Author get() {
            return new Author();
        }
    });
}

改用 Lambda 表达式

public static void main(String[] args) {
    Author author = getAuthor();
    Optional<Author> optionalAuthor = Optional.of(author);
    Author author2 = optionalAuthor.orElseGet(() -> new Author());
}

orElseThrow

orElseThrow(Supplier<? extends X> exceptionSupplier):如果存在则返回值,否则抛出 Supplier 指定的异常。

该方法在 Spring 框架中用的比较多,可以用 Spring 来做统一的异常捕获。

public static void main(String[] args) throws Throwable {
    Author author = getAuthor();
    Optional<Author> optionalAuthor = Optional.of(author);
    Author author2 = optionalAuthor.orElseThrow(new Supplier<Throwable>() {
        @Override
        public Throwable get() {
            return new RuntimeException();
        }
    });
}

改为 Lambda 表达式:

public static void main(String[] args) throws Throwable {
    Author author = getAuthor();
    Optional<Author> optionalAuthor = Optional.of(author);
    Author author2 = optionalAuthor.orElseThrow(() -> new RuntimeException());
}

5. 过滤

filter(Predicate<? super T> predicate):如果存在值并且值满足设定的条件,返回满足条件的元素组成的 Optional,否则返回空的 Optional。

public static void main(String[] args) throws Throwable {
    Author author = getAuthor();
    Optional<Author> optionalAuthor = Optional.ofNullable(author);
    optionalAuthor.filter(author1 -> author1.getAge() > 18).ifPresent(author1 -> System.out.println(author1.getName()));
}

6. 判断

isPresent():对Optional对象中是否存在值进行判断,如果存在返回true,否则返回false。

一般都是直接使用 ifPresent(),单独使用 isPresent() 和使用 if(xx != null) 差不多。

7. 数据转换

map(Function<? super T,? extends U> mapper):与 Stream 中的 map() 方法类似,用于数据转换或计算。

例如获取作家书籍的集合:

public static void main(String[] args) throws Throwable {
    Optional<Author> optionalAuthor = Optional.ofNullable(getAuthor());
    // 使用map将 Optional<Author> 转换为 Optional<List<Book>>
    Optional<List<Book>> optionalBooks = optionalAuthor.map(new Function<Author, List<Book>>() {
        @Override
        public List<Book> apply(Author author) {
            return author.getBooks();
        }
    });
    // 如果Optional<List<Book>> 不为空,取出数据进行消费。
    optionalBooks.ifPresent(new Consumer<List<Book>>() {
        @Override
        public void accept(List<Book> books) {
            // 遍历books
            books.forEach(new Consumer<Book>() {
                @Override
                public void accept(Book book) {
                    System.out.println(book.getName());
                }
            });
        }
    });
}

改为 Lambda 表达式:

public static void main(String[] args) throws Throwable {
    Optional<Author> optionalAuthor = Optional.ofNullable(getAuthor());
    Optional<List<Book>> optionalBooks = optionalAuthor.map(author -> author.getBooks());
    optionalBooks.ifPresent(books -> books.forEach(book -> System.out.println(book.getName())));
}