网站公告

本次更新:没啥更新的

“钱塘江上潮信起,今日方知我是我”

Skip to content

Optional ​

字数: 0 字 时长: 0 分钟

1. 概述 ​

在 Java 开发中,空指针异常 NullPointerException 非常常见,因此经常需要进行非空判断。

例如:

java
Author author = getAuthor();

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

如果对象内部还包含其他对象,非空判断可能会越来越多,导致代码出现大量嵌套:

text
对象
 ↓
判断是否为空
 ↓
获取属性
 ↓
再次判断是否为空
 ↓
继续处理

JDK 8 引入了 Optional,用于表示:

一个值可能存在,也可能不存在。

通过 Optional 提供的方法,可以减少直接操作 null 时产生的空指针问题,并且很多函数式编程相关 API 也会使用 Optional。


2. 创建 Optional ​

Optional 可以理解为一个容器,用来包装一个可能为空的数据。

最常用的创建方式:

java
Optional.ofNullable(value)

2.1 ofNullable() ​

Optional.ofNullable() 无论传入的值是否为 null,都可以正常创建 Optional。

java
public class OptionalDemo {

    public static void main(String[] args) {
        Author author = getAuthor();

        Optional<Author> authorOptional = Optional.ofNullable(author);

        authorOptional.ifPresent(
                value -> System.out.println(value.getName())
        );
    }

    private static Author getAuthor() {
        Author author = new Author(
                1L,
                "蒙多",
                33,
                "一个从菜刀中明悟哲理的祖安人",
                null
        );

        return null;
    }
}

即使 getAuthor() 返回 null,也不会因为创建 Optional 而出现空指针异常。

实际使用时,也可以直接让方法返回 Optional:

java
public static void main(String[] args) {
    Optional<Author> authorOptional = getAuthorOptional();

    authorOptional.ifPresent(
            author -> System.out.println(author.getName())
    );
}

private static Optional<Author> getAuthorOptional() {
    Author author = new Author(
            1L,
            "蒙多",
            33,
            "一个从菜刀中明悟哲理的祖安人",
            null
    );

    return Optional.ofNullable(author);
}

这样调用方看到返回值:

java
Optional<Author>

就能够明确知道:

这个方法可能返回一个 Author,也可能没有数据。

在实际开发中,数据库查询也可能返回空数据。MyBatis 3.5 之后可以支持方法返回 Optional,可以直接将 DAO / Mapper 方法的返回值声明为 Optional<T>。


2.2 of() ​

如果可以确定数据一定不为 null,可以使用:

java
Optional.of(value)

例如:

java
public static void main(String[] args) {
    Author author = getAuthor();

    Optional<Author> authorOptional = Optional.of(author);

    authorOptional.ifPresent(
            value -> System.out.println(value.getName())
    );
}

private static Author getAuthor() {
    return new Author(
            1L,
            "蒙多",
            33,
            "一个从菜刀中明悟哲理的祖安人",
            null
    );
}

需要注意:

Optional.of() 不能接收 null,否则会直接抛出 NullPointerException。

例如:

java
Optional<Author> optional = Optional.of(null);

会报错:

image-20240425222842525

因此,如果数据可能为空,通常使用:

java
Optional.ofNullable(value)

更加合适。


2.3 ofNullable() 原理 ​

查看 ofNullable() 的源码:

image-20240425223322891

核心逻辑可以理解为:

java
return value == null
        ? Optional.empty()
        : Optional.of(value);

也就是:

text
value == null
      ↓
Optional.empty()

value != null
      ↓
Optional.of(value)

2.4 empty() ​

Optional.empty() 用于创建一个内部没有数据的 Optional。

例如:

java
private static Optional<Author> getAuthorOptional() {
    Author author = new Author(
            1L,
            "蒙多",
            33,
            "一个从菜刀中明悟哲理的祖安人",
            null
    );

    return author == null
            ? Optional.empty()
            : Optional.of(author);
}

实际上这段代码可以直接使用:

java
return Optional.ofNullable(author);

因此一般情况下,直接使用 ofNullable() 会更加简洁。


3. 安全消费值 ​

3.1 ifPresent() ​

获取到 Optional 后,如果只希望在数据存在时执行某个操作,可以使用:

java
ifPresent()

它会判断内部是否存在数据:

text
存在数据
   ↓
执行 Consumer

没有数据
   ↓
什么都不执行

例如:

java
public static void main(String[] args) {
    Optional<Author> authorOptional = getAuthorOptional();

    authorOptional.ifPresent(
            author -> System.out.println(author.getName())
    );
}

private static Optional<Author> getAuthorOptional() {
    Author author = new Author(
            1L,
            "蒙多",
            33,
            "一个从菜刀中明悟哲理的祖安人",
            null
    );

    return Optional.ofNullable(author);
}

相比:

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

可以直接通过 Optional 对数据进行安全消费。


4. 获取值 ​

4.1 get() ​

可以通过 get() 直接获取 Optional 中的数据:

java
Optional<Author> authorOptional = getAuthorOptional();

Author author = authorOptional.get();

System.out.println(author);

例如:

java
private static Optional<Author> getAuthorOptional() {
    return Optional.ofNullable(null);
}

此时再执行:

java
authorOptional.get();

会抛出异常。

image-20250324202616104

因此:

不推荐在无法确定 Optional 一定有值时直接使用 get()。


5. 安全获取值 ​

相比直接使用 get(),可以通过 Optional 提供的方法处理数据为空的情况。

5.1 orElseGet() ​

orElseGet() 用于:

有值就返回原来的值,没有值就通过 Supplier 创建一个默认值。

完整写法:

java
public static void main(String[] args) {
    Optional<Author> authorOptional = getAuthorOptional();

    Author author = authorOptional.orElseGet(
            new Supplier<Author>() {
                @Override
                public Author get() {
                    return new Author();
                }
            }
    );
}

private static Optional<Author> getAuthorOptional() {
    Author author = new Author(
            1L,
            "蒙多",
            33,
            "一个从菜刀中明悟哲理的祖安人",
            null
    );

    return Optional.ofNullable(null);
}

Lambda 简化:

java
public static void main(String[] args) {
    Optional<Author> authorOptional = getAuthorOptional();

    Author author = authorOptional.orElseGet(
            () -> new Author(
                    1L,
                    "锐雯",
                    33,
                    "一个从菜刀中明悟哲理的祖安人",
                    null
            )
    );
}

执行逻辑:

text
Optional 有值
     ↓
返回原来的值

Optional 没有值
     ↓
执行 Supplier
     ↓
返回默认值

例如:

text
有蒙多
  ↓
返回蒙多

没有数据
  ↓
返回锐雯

5.2 orElseThrow() ​

orElseThrow() 用于:

有值就返回数据,没有值就抛出指定异常。

完整写法:

java
Optional<Author> authorOptional = getAuthorOptional();

Author author = authorOptional.orElseThrow(
        new Supplier<RuntimeException>() {
            @Override
            public RuntimeException get() {
                return new RuntimeException("数据为空");
            }
        }
);

Lambda 简化:

java
Author author = authorOptional.orElseThrow(
        () -> new RuntimeException("数据为空")
);

这种方式在业务开发中比较常见,例如:

java
User user = userOptional.orElseThrow(
        () -> new RuntimeException("用户不存在")
);

抛出的异常可以继续交给 Spring 的统一异常处理机制进行处理。


6. 过滤 ​

6.1 filter() ​

Optional 也提供了 filter(),可以对内部的数据进行条件过滤。

如果:

  • Optional 本身没有值
  • 或者有值但不满足条件

最终都会得到一个空的 Optional。

例如:只处理年龄大于 18 岁的作者。

完整写法:

java
public static void main(String[] args) {
    Optional<Author> authorOptional = getAuthorOptional();

    authorOptional
            .filter(new Predicate<Author>() {
                @Override
                public boolean test(Author author) {
                    return author.getAge() > 18;
                }
            })
            .ifPresent(
                    author -> System.out.println(author.getName())
            );
}

private static Optional<Author> getAuthorOptional() {
    Author author = new Author(
            1L,
            "蒙多",
            33,
            "一个从菜刀中明悟哲理的祖安人",
            null
    );

    return Optional.ofNullable(author);
}

Lambda 简化:

java
Optional<Author> authorOptional = getAuthorOptional();

authorOptional
        .filter(author -> author.getAge() > 18)
        .ifPresent(author -> System.out.println(author.getName()));

执行过程:

text
Optional<Author>
      ↓
age > 18 ?
   ↙       ↘
 满足      不满足
  ↓          ↓
保留      Optional.empty()

7. 判断是否存在数据 ​

7.1 isPresent() ​

isPresent() 用于判断 Optional 内部是否存在数据。

java
Optional<Author> authorOptional = getAuthorOptional();

if (authorOptional.isPresent()) {
    System.out.println(authorOptional.get().getName());
}

返回结果:

text
有值    → true

无值    → false

虽然可以这样使用,但:

java
if (optional.isPresent()) {
    optional.get();
}

本质上又回到了手动判断的写法。

如果只是有值时执行操作,通常更推荐:

java
authorOptional.ifPresent(
        author -> System.out.println(author.getName())
);

8. 数据转换 ​

8.1 map() ​

Optional 提供了 map(),可以对内部的数据进行转换。

转换后的结果仍然会被 Optional 包装。

例如:

text
Optional<Author>
       ↓
map()
       ↓
Optional<List<Book>>

完整写法:

java
public static void main(String[] args) {
    Optional<Author> authorOptional = getAuthorOptional();

    authorOptional
            .map(new Function<Author, List<Book>>() {
                @Override
                public List<Book> apply(Author author) {
                    return author.getBooks();
                }
            })
            .ifPresent(
                    new Consumer<List<Book>>() {
                        @Override
                        public void accept(List<Book> books) {
                            System.out.println(books);
                        }
                    }
            );
}

private static Optional<Author> getAuthorOptional() {
    Author author = new Author(
            1L,
            "蒙多",
            33,
            "一个从菜刀中明悟哲理的祖安人",
            null
    );

    return Optional.ofNullable(author);
}

Lambda 简化:

java
public static void main(String[] args) {
    Optional<Author> authorOptional = getAuthorOptional();

    authorOptional
            .map(author -> author.getBooks())
            .ifPresent(books -> System.out.println(books));
}

执行过程:

text
Optional<Author>
       ↓
获取 books
       ↓
Optional<List<Book>>
       ↓
ifPresent()
       ↓
使用 books

如果原来的 Optional 没有值,map() 中的转换逻辑也不会执行。


9. 常用方法小结 ​

方法作用
ofNullable()将可能为 null 的数据包装成 Optional
of()包装确定不为 null 的数据
empty()创建一个空的 Optional
ifPresent()有值时执行操作
get()直接获取值,不推荐盲目使用
orElseGet()无值时通过 Supplier 提供默认值
orElseThrow()无值时抛出异常
filter()对内部数据进行过滤
isPresent()判断是否存在数据
map()转换内部的数据

可以简单理解为:

text
ofNullable()
    ↓
包装可能为空的数据

ifPresent()
    ↓
有值就执行

orElseGet()
    ↓
没值就给默认值

orElseThrow()
    ↓
没值就抛异常

filter()
    ↓
过滤

map()
    ↓
转换

Optional 的核心不是“消灭 null”,而是明确表达一个值可能不存在,并提供一套更安全的方式处理这种情况。

Noctiflorous Garden · MIT License