Java 中如何使用 Optional

更新于 2025-12-27

尽管存在争议,Optional 极大地改善了 Java 应用程序的设计。在本文中,我们将探讨如何、何时以及在何处最佳地应用 Optional。

Justin Albano 2020-06-16

引言

Optional 类的引入是 Java 语言设计上的一大改进,但这一改进颇具争议。在 Optional 出现之前,许多开发者要么使用 null,要么抛出异常来表示期望的值不存在;而通过使用 Optional 类,我们可以明确地表达某个值可能存在,也可能不存在。尽管如此,如果 Optional 被不当使用,反而可能带来更多问题。

在本文中,我们将探讨 Optional 类的基础知识,包括:

  • 引入它的目的及其背后的设计思路
  • Optional 类中包含的核心方法
  • 何时何地适合使用 Optional
  • 可替代的其他技术方案

目的

Java 和大多数面向对象(OO)语言一样,在其类型系统中存在一个“隐秘后门”:任何非基本类型都可以被赋值为 null

例如,假设我们有如下方法签名:

public Foo doSomething();

很明显,该方法返回一个 Foo 类型的对象,但它也可以返回另一个值:null。由于任何非基本类型都可以设为 null,开发者完全可以写出如下实现:

public Foo doSomething() {
    return null; 
}

空值(Nullity)问题

这给调用该方法的客户端带来了沉重负担。在使用从 doSomething() 返回的 Foo 对象之前,客户端必须先检查它是否为 null

Foo foo = doSomething();

if (foo == null) {
    // 处理 null 情况...
} else {
    // 使用 foo 对象...
}

这种做法可以避免 NullPointerException(NPE),但也带来新问题:任何非基本类型对象都可能隐式地为 null。因此,理论上我们必须对每个方法返回的对象都进行空值检查。这不仅在大型应用中不可行,还会使代码变得混乱。例如,大量空值检查会引入样板代码,掩盖业务逻辑的清晰性。

根本问题在于:我们无法知道某个方法是否“有意”返回 null(比如找不到所需值时),还是“保证永不返回 null。由于不确定,我们只能假设所有返回对象都可能是 null

一种常见解决方案是使用 JavaDoc 注释说明返回值可能为 null。虽然比什么都不做强,但编译器并不会强制客户端进行空值检查。类似地,像 @NotNull 这样的注解也存在同样缺陷——它们无法在编译期强制执行。


Optional 类

我们需要一种机制,让方法开发者能显式声明返回值可能存在,也可能不存在。这一机制在 JDK 8(原文误写为 JDK 7,实际为 JDK 8)中以 Optional 类的形式引入。

Optional 是一个包装类,用于封装一个可能不存在的对象。因此,如果我们的 doSomething() 方法可能无法返回有效的 Foo 对象,我们可以将其签名改为:

public Optional<Foo> doSomething();

如后文所示,Optional 提供了一套方法(很多是函数式的),让客户端可以决定在值不存在时该如何处理。例如,我们可以使用 orElse 方法在值不存在时返回默认值(在 Optional 术语中称为“空 Optional”):

Foo foo = doSomething()
    .orElse(new Foo());

或者使用 orElseThrowOptional 为空时抛出异常:

Foo foo = doSomething()
    .orElseThrow(SomeException::new);

需要注意两点:

  1. Java 编译器强制我们处理空 Optional 的情况
  2. 客户端负责处理缺失值

与文档和注解不同,Optional 要求客户端必须做出决策。例如,以下代码将无法编译:

Foo foo = doSomething(); // 错误:类型不匹配

编译器会报错:Optional<Foo> 不能直接赋值给 Foo。我们必须调用 orElseorElseThrowget(但 get 不推荐作为首选)等方法,将 Optional<Foo> 转换为 Foo。而这些方法通常需要参数,从而迫使我们明确指定默认值或异常类型。


客户端责任

这引出了第二点:处理空 Optional 的责任在客户端

通过返回 Optional<Foo> 而非 FoodoSomething() 方法实际上是在告诉客户端:“我可能找不到结果”。因此,客户端必须处理“无结果”的情况。

这种设计意味着:方法开发者没有足够信息来决定缺失值时该做什么。虽然开发者可以抛出异常,但“值不存在”未必是异常情况。

例如,如果我们想“查找对象,若不存在则创建一个”,那么对象不存在并不是错误,抛异常反而不合适。若使用异常方式:

public Foo findIfExists() throws FooNotFoundException;

使用时需这样写:

Foo foo = null;
try {
    foo = findIfExists();
} catch (FooNotFoundException e) {
    foo = // 创建默认值...
}

而如果返回 Optional

public Optional<Foo> findIfExists();

则只需:

Foo foo = findIfExists()
    .orElse(/* 创建默认值... */);

后者可读性更强:仅看代码就能理解“若存在则用之,否则用这个值”。前者则需通过理解 catch 块才能明白其含义。因此,Optional 的语义更贴近意图。

总结:当“值缺失”不是错误,且客户端应负责处理时,Optional 是有效手段。若缺失值确实代表严重错误,则应抛出异常。


null 的 Optional 对象?

有一个重要注意事项:Optional 对象本身也可能为 null

因为 Optional 本身是非基本类型,所以以下代码能编译:

public Optional<Foo> doSomething() {
    return null;
}

这会导致客户端既要处理 null 返回值,又要处理空 Optional

Optional<Foo> possibleFoo = doSomething();

if (possibleFoo == null) {
    // 处理缺失...
} else {
    Foo foo = possibleFoo.orElse(/* 处理缺失... */); 
}

这不仅造成逻辑重复,还重新引入了空值检查的混乱。

根据 Optional 官方文档

类型为 Optional 的变量本身绝不应为 null;它应始终指向一个 Optional 实例。

如果方法返回 null 而不是 Optional 实例,这是违反契约的行为。既然方法声明返回 Optional,就应保证永不返回 null。若无值,应返回 Optional.empty()

因此,我们应假定 Optional 对象永远不会为 null。若实际出现 null,这是方法实现者的错误,而非客户端的责任。


核心方法

理解了 Optional 的设计理念后,我们来看其实际用法。Optional 的方法可分为两类:创建方法实例方法

创建方法

of

包装非空对象:

Optional<Foo> foo = Optional.of(new Foo()); // 成功
Optional<Foo> foo = Optional.of(null);      // 抛出 NullPointerException

ofNullable

安全包装可能为 null 的对象:

Optional<Foo> foo1 = Optional.ofNullable(new Foo()); // 包装
Optional<Foo> foo2 = Optional.ofNullable(null);      // 返回 empty()

empty

创建空 Optional

Optional<Foo> foo = Optional.empty();
// 等价于 Optional.ofNullable(null)

实例方法

isPresent()isEmpty()

  • isPresent():有值返回 true
  • isEmpty()(JDK 11+):无值返回 true
populated.isPresent(); // true
populated.isEmpty();   // false

empty.isPresent();     // false
empty.isEmpty();       // true

get()

  • 有值时返回值
  • 无值时抛出 NoSuchElementException

慎用!通常需先检查 isPresent(),但这又回到了 null 检查的老路:

if (possibleFoo.isPresent()) {
    Foo foo = possibleFoo.get();
    // ...
}

建议优先使用 orElseorElseThrow 等方法

Brian Goetz(Java 语言架构师)曾说:

“我们在 Java 8 中犯的一个错误就是命名了 Optional.get(),因为它诱使人们在不调用 isPresent() 的情况下直接调用它,完全违背了使用 Optional 的初衷。”

JDK 10 引入了 orElseThrow() 作为 get() 的语义更清晰的替代。

orElse 系列

  • orElse(T other):立即计算默认值
  • orElseGet(Supplier<? extends T> supplier):惰性计算默认值(仅在需要时)
Foo foo = possibleFoo.orElse(new Foo()); // 总是创建新对象

Foo foo = possibleFoo.orElseGet(() -> expensiveDefault()); // 仅在为空时调用

orElseThrow 系列

  • orElseThrow():抛出 NoSuchElementException
  • orElseThrow(Supplier<X> exceptionSupplier):抛出自定义异常
Foo foo = possibleFoo.orElseThrow();
Foo foo = possibleFoo.orElseThrow(SomeException::new);

ifPresent 系列

  • ifPresent(Consumer<T> action):有值时执行操作
  • ifPresentOrElse(Consumer<T>, Runnable):有值执行前者,无值执行后者
possibleFoo.ifPresent(foo -> foo.doSomething());

possibleFoo.ifPresentOrElse(
    foo -> foo.update(),
    () -> log("No foo found")
);

map

对值进行转换(函数式管道):

Optional<Person> person = db.findPerson()
    .map(mapper::fromDto);

⚠️ 注意:不要在 map 中返回 null 来表示“无值”,这违背了 Optional 的初衷。

flatMap

当转换本身可能失败时使用。flatMap 的函数应返回 Optional

Optional<Person> person = db.findPerson()
    .flatMap(dto -> {
        if (canConvert(dto)) {
            return Optional.ofNullable(mapper.fromDto(dto));
        } else {
            return Optional.empty();
        }
    });

filter

根据条件过滤:

Optional.of(new Bar(1))
    .filter(bar -> bar.getNumber() > 0)
    .isPresent(); // true

Optional.of(new Bar(-1))
    .filter(bar -> bar.getNumber() > 0)
    .isPresent(); // false

stream()(JDK 9+)

Optional 转为 Stream

Set<Person> people = findPerson().stream()
    .filter(...)
    .map(...)
    .collect(Collectors.toSet());

何时使用,何时不用?

✅ 推荐:作为方法返回值

官方文档明确指出:

Optional 主要用于方法返回类型,当需要明确表示“无结果”且使用 null 极易引发错误时。

适用场景:

  • 值可能存在也可能不存在
  • 值缺失不是错误
  • 客户端应负责处理缺失情况

典型例子:Repository 查询

public interface BookRepository {
    Optional<Book> findById(long id);
    Optional<Book> findByTitle(String title);
}

❌ 不推荐:作为字段(成员变量)

public class Bar {
    private Optional<Foo> foo; // 不推荐!
}

原因:

  • Optional 不可序列化
  • 它不是通用的“Maybe”类型,而是专为返回值设计

正确做法:字段用普通类型,getter 返回 Optional

public class Bar {
    private Foo foo;

    public Optional<Foo> getFoo() {
        return Optional.ofNullable(foo);
    }
}

❌ 不推荐:作为方法参数

public void doSomething(Optional<Foo> foo) { ... } // 不推荐

应使用方法重载不同方法名

public void doSomething() { ... }
public void doSomething(Foo foo) { ... }

// 或
public void doSomething() { ... }
public void doSomethingWithFoo(Foo foo) { ... }

替代方案

1. 使用 null

仅在性能极度敏感(如底层网络/驱动代码)时考虑。绝大多数应用中,Optional 的开销微不足道。

2. 空对象模式(Null Object)

创建一个特殊子类,实现“无值”时的行为:

public class NullArticle extends Article {
    @Override
    public void submit() {
        throw new ArticleNotFoundException();
    }
}

// Repository 返回 NullArticle 而非 null

适用于方法自身知道如何处理缺失值的场景。

3. 抛出异常

当“值不存在”确实是错误时:

public Article findById(long id) {
    if (found) return article;
    else throw new ArticleNotFoundException();
}

结论

在应用程序中,值可能存在也可能不存在,如何优雅地处理这种情况是良好设计的关键。自 JDK 8 起,Java 提供了 Optional 类,让开发者能明确表达“可能无值”,并让客户端根据上下文决定如何处理。

关键原则

  • Optional 仅用于方法返回值
  • 绝不用于字段或参数
  • 避免 get(),优先使用 orElseorElseThrowifPresent
  • 永远不要返回 nullOptional

掌握 Optional 的正确用法,是精通现代 Java 的重要一步。