Java 基础体系 · 第 47/100 篇。示例统一以 Java 25 LTS 为语言和 JVM 基线;框架示例使用与其兼容的现代稳定版本。

Java Optional:创建、组合、空值边界、性能与错误用法

Optional<T> 是 Java 标准库中用于表达“一个结果可能存在,也可能不存在”的容器类型。它解决的不是所有空指针问题,也不是把所有变量都包装起来,而是把缺失值作为返回结果的一部分显式表达

在 Java 25 LTS 中,Optional 仍然是 java.util 包中的最终类,主要配套类型还有:

  • Optional<T>:引用类型;
  • OptionalIntint
  • OptionalLonglong
  • OptionalDoubledouble

本文中的示例均面向 Java 25,使用标准 API,不依赖第三方库。


一、Optional 表达的到底是什么

设函数 findUser(id) 的结果可能有两种状态:

findUser:IdUser{不存在}findUser : Id \rightarrow User \cup \{不存在\}

传统 Java 写法通常用 null 表示“不存在”:

User user = findUser(id);
if (user != null) {
    // 使用 user
}

这里的问题是,null 既没有携带“这是允许缺失的结果”这一语义,也无法阻止调用方忘记判断。

使用 Optional<User> 后,函数返回值的类型本身表达了两种状态:

Optional<User> user = findUser(id);

可以把 Optional<T> 理解为:

Optional<T> = Empty | Present(T)

其中:

  • Empty 表示没有值;
  • Present(T) 表示存在一个非 nullT

因此,Optional 不能表示 Present(null)。它只有“无值”和“有一个非空值”两种状态。

这一区别很重要:

Optional<String> empty = Optional.empty();
Optional<String> present = Optional.of("Java");

而下面的代码非法:

Optional<String> invalid = Optional.of(null); // NullPointerException

如果输入值本来就可能为 null,应使用:

Optional<String> result = Optional.ofNullable(possiblyNull);

ofNullable(null) 得到 Optional.empty()ofNullable("Java") 得到包含 "Java"Optional


二、创建 Optional:emptyofofNullable

2.1 Optional.empty()

Optional<String> noValue = Optional.empty();
System.out.println(noValue.isEmpty()); // true

empty() 创建一个没有值的 Optional

规范保证的是它表示空状态,而不是每次调用都返回同一个对象。因此不能依赖引用相等:

Optional<String> a = Optional.empty();
Optional<String> b = Optional.empty();

System.out.println(a == b); // 不应依赖这个结果
System.out.println(a.equals(b)); // true

Optional 是 value-based class。它的语义重点是所表示的值,而不是对象身份。不要使用 ==、对象身份哈希,或对 Optional 实例加锁来建立业务逻辑。


2.2 Optional.of(value)

Optional<String> language = Optional.of("Java");

of 的前置条件是参数不为 null

Optional<String> value = Optional.of(null); // 运行时抛出 NullPointerException

of 适用于调用者已经能够证明值一定存在的场景。例如:

String requiredName = Objects.requireNonNull(config.name());
Optional<String> name = Optional.of(requiredName);

这里的含义是:如果 namenull,这是违反方法前置条件的错误,而不是一个正常的“没有结果”状态。


2.3 Optional.ofNullable(value)

ofNullable 用于把一个传统的、可能返回 null 的边界转换为 Optional

String raw = System.getenv("APP_ENV");
Optional<String> environment = Optional.ofNullable(raw);

其逻辑等价于:

Optional<String> result =
        raw == null ? Optional.empty() : Optional.of(raw);

但实际使用中不应手写这段条件,因为 ofNullable 已经明确表达了“把可空值转换为空容器或非空容器”的意图。

典型边界包括:

  • 调用旧式 API;
  • 读取环境变量;
  • 读取配置;
  • 从数据库查询单条记录;
  • 从缓存查询;
  • 解析可能缺失的字段。

例如,一个传统仓储接口:

final class UserRepository {
    User findById(long id) {
        return id == 42 ? new User(42, "Ada") : null;
    }
}

record User(long id, String name) {}

可以在服务边界立即转换:

final class UserService {
    private final UserRepository repository = new UserRepository();

    Optional<User> findUser(long id) {
        return Optional.ofNullable(repository.findById(id));
    }
}

这样,null 只存在于仓储实现与服务边界内部,调用方不再需要猜测返回值是否可能为 null


三、读取 Optional:存在性检查与取值

3.1 isPresent()isEmpty()

Optional<String> value = Optional.of("Java");

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

if (!value.isEmpty()) {
    System.out.println(value.get());
}

isPresent() 在有值时返回 trueisEmpty() 在无值时返回 true。两者都只检查状态,不会转换或生成新值。

但是,下面这种写法通常只是把 null 检查改成了另一种形式:

if (value.isPresent()) {
    process(value.get());
}

它并非错误,但如果目的是执行一个动作,ifPresent 更直接:

value.ifPresent(this::process);

如果两种状态都要处理,可以使用:

value.ifPresentOrElse(
        this::process,
        () -> System.out.println("用户不存在")
);

ifPresentOrElse 的两个分支中只会执行一个:

  • 有值时执行第一个动作;
  • 空值时执行第二个动作。

动作本身抛出的异常不会被 Optional 吞掉,而是继续向上传播。


3.2 get():存在时取值,空时失败

Optional<String> value = Optional.of("Java");
String text = value.get(); // "Java"

如果容器为空:

Optional<String> empty = Optional.empty();
empty.get(); // NoSuchElementException

get() 的问题不在于它永远不能使用,而在于它把“调用前必须存在”的条件留给了调用者。如果这个条件来自明确的前置检查,使用可以成立:

if (value.isPresent()) {
    String text = value.get();
}

但在大多数“缺失是正常业务结果”的场景中,应使用后文的 orElseorElseGetorElseThrow,让缺失路径更清楚。


四、默认值:orElseorElseGet 的关键差异

4.1 orElse 总会计算参数

String name = optionalName.orElse(loadDefaultName());

很多人把它理解为:

如果 optionalName 有值,就不执行 loadDefaultName()

这是错误的。Java 方法参数在调用方法前就会求值,因此 loadDefaultName() 无论 optionalName 是否为空都会执行。

static String loadDefaultName() {
    System.out.println("加载默认名称");
    return "Guest";
}

public static void main(String[] args) {
    Optional<String> name = Optional.of("Ada");

    String result = name.orElse(loadDefaultName());

    System.out.println(result);
}

输出顺序为:

加载默认名称
Ada

结果是 "Ada",但默认值已经被计算。


4.2 orElseGet 延迟计算默认值

String name = optionalName.orElseGet(() -> loadDefaultName());

orElseGet 接受一个 Supplier<? extends T>

  • 有值时直接返回已有值,不调用 supplier;
  • 空值时调用 supplier,并返回其结果。
Optional<String> name = Optional.of("Ada");

String result = name.orElseGet(() -> {
    System.out.println("加载默认名称");
    return "Guest";
});

System.out.println(result);

输出只有:

Ada

因为 Optional 有值,supplier 没有执行。

两者的选择条件是执行语义,而不是简单的性能口诀:

// 默认值是常量,直接使用 orElse 很清楚
String value = optional.orElse("unknown");

// 默认值需要计算、读取文件或访问服务,使用 orElseGet
String value = optional.orElseGet(this::queryFallback);

如果默认值计算本身有副作用、可能抛异常或成本较高,错误地使用 orElse 会导致:

  • 不必要的数据库查询;
  • 不必要的对象创建;
  • 即使主值存在也触发异常;
  • 日志、计数器等副作用错误发生。

例如:

Optional<String> primary = Optional.of("primary");

String result = primary.orElse(fail());

fail() 仍会执行,异常会在调用 orElse 之前抛出,主值也无法保护这次调用。


4.3 orElseThrow:缺失是错误时显式失败

无参数版本:

String name = optionalName.orElseThrow();

为空时抛出 NoSuchElementException

带异常 supplier 的版本:

User user = userService.findUser(42)
        .orElseThrow(() -> new UserNotFoundException(42));

这里有两个阶段:

  1. findUser(42) 返回 Optional<User>
  2. 如果为空,才调用异常 supplier 并抛出异常。

异常 supplier 只应生成异常,不应承担复杂业务逻辑。若 supplier 返回 null,运行时会产生 NullPointerException,因为 orElseThrow 要求它提供一个异常对象。


五、转换与组合:mapflatMapfilter

Optional 的核心价值不只是“少写一次 null 判断”,而是可以把一系列可能失败的操作组合成一条有明确空值传播规则的链。

设:

Optional<T> = Empty | Present(T)

对于一个普通映射函数:

f: T -> R

map(f) 的结果为:

map(f, Empty)       = Empty
map(f, Present(x))  = Present(f(x))

但由于 Optional 不允许保存 null,如果 f(x) 返回 null,结果会被转换为 Empty


5.1 map:把已有值转换成另一个值

Optional<String> name = Optional.of("Ada");

Optional<Integer> length = name.map(String::length);

System.out.println(length); // Optional[3]

如果源为空,映射函数不会执行:

Optional<String> empty = Optional.empty();

Optional<Integer> length = empty.map(String::length);

System.out.println(length); // Optional.empty

如果映射函数返回 null

Optional<String> name = Optional.of("Ada");

Optional<String> result = name.map(value -> null);

System.out.println(result.isEmpty()); // true

这相当于:

Optional<String> result = Optional.ofNullable(function.apply(value));

但映射函数本身不能是 null

name.map(null); // NullPointerException

函数参数为 null 是调用错误,不是“映射结果为空”。


5.2 filter:值存在且满足条件时保留

filter 接受谓词:

Optional<String> name = Optional.of("Ada");

Optional<String> result = name.filter(value -> value.length() >= 3);

System.out.println(result); // Optional[Ada]

其规则是:

filter(p, Empty)       = Empty
filter(p, Present(x))  = Present(x),如果 p(x) 为 true
filter(p, Present(x))  = Empty,如果 p(x) 为 false

例如:

Optional<Integer> age = Optional.of(15);

Optional<Integer> adultAge = age.filter(value -> value >= 18);

System.out.println(adultAge); // Optional.empty

filter 不改变值的类型,只改变值是否继续存在。谓词为 null 是错误;谓词执行时抛出的异常也会直接传播。


5.3 flatMap:连接返回 Optional 的操作

假设有两个可能缺失的函数:

Optional<User> findUser(long id);
Optional<Address> findAddress(User user);

如果错误地使用 map

Optional<Optional<Address>> nested =
        findUser(42).map(this::findAddress);

结果类型是:

Optional<Optional<Address>>

外层表示用户是否存在,内层表示地址是否存在。这通常不是调用方想要的结构。

flatMap 用于消除这一层嵌套:

Optional<Address> address =
        findUser(42).flatMap(this::findAddress);

其形式化规则为:

f: T -> Optional<R>

flatMap(f, Empty)       = Empty
flatMap(f, Present(x))  = f(x)

完整示例:

record User(long id, String name) {}
record Address(String city) {}

Optional<User> findUser(long id) {
    return id == 42
            ? Optional.of(new User(42, "Ada"))
            : Optional.empty();
}

Optional<Address> findAddress(User user) {
    return user.name().equals("Ada")
            ? Optional.of(new Address("London"))
            : Optional.empty();
}

Optional<Address> result =
        findUser(42).flatMap(this::findAddress);

数据流如下:

flowchart LR
    A[findUser] --> B{用户存在?}
    B -- 否 --> E[Optional.empty]
    B -- 是 --> C[flatMap findAddress]
    C --> D{地址存在?}
    D -- 否 --> E
    D -- 是 --> F[Optional<Address>]

flatMap 的函数必须返回非 nullOptional。下面的代码不是“返回空结果”,而是违反了函数契约:

findUser(42).flatMap(user -> null); // NullPointerException

正确写法是:

findUser(42).flatMap(user -> Optional.empty());

六、多个 Optional 的组合

6.1 顺序依赖:使用 flatMapmap

如果后一步依赖前一步的结果:

Optional<String> city =
        findUser(42)
                .flatMap(this::findAddress)
                .map(Address::city);

假设:

findUser(42)       = Present(user)
findAddress(user)  = Present(address)
address.city()     = "London"

中间状态为:

Present(user)
    -> Present(address)
    -> Present("London")

如果任一步为空:

Empty
    -> Empty
    -> Empty

后续的 mapflatMap 不会执行。这个特性使得空值可以沿着链自动传播。


6.2 备选来源:使用 or

or 表达“当前没有值时,尝试另一个 Optional 来源”:

Optional<User> user = findFromCache(id)
        .or(() -> findFromDatabase(id));

它与 orElseGet 不同:

  • orElseGet 最终返回 T
  • or 返回另一个 Optional<T>,因此可以继续进行 Optional 链式操作。

语义为:

or( Present(x), supplier) = Present(x)
or( Empty, supplier)       = supplier()

supplier 只有当前一个 Optional 为空时才执行:

Optional<String> result = Optional.of("cache")
        .or(() -> {
            System.out.println("查询数据库");
            return Optional.of("database");
        });

System.out.println(result.get()); // cache

不会输出“查询数据库”。

如果 supplier 返回 null,这是错误:

Optional<String> result =
        Optional.<String>empty().or(() -> null); // NullPointerException

应返回 Optional.empty() 或一个包含值的 Optional。


6.3 同时依赖多个值:没有标准 zip

Java 的 Optional 没有内置 zip 方法。假设要同时获得用户和订单:

Optional<User> user = findUser(42);
Optional<Order> order = findOrder(42);

只有两者都存在时才构造结果,可以这样写:

record UserOrder(User user, Order order) {}

Optional<UserOrder> combined =
        user.flatMap(u ->
                order.map(o -> new UserOrder(u, o)));

推导过程是:

  1. user 为空:外层 flatMap 直接得到 Optional.empty()
  2. user 有值:进入 lambda;
  3. order 为空:内层 map 得到 Optional.empty()
  4. 两者都有值:构造 UserOrder,得到 Optional<UserOrder>

如果使用 map 代替外层 flatMap

Optional<Optional<UserOrder>> nested =
        user.map(u -> order.map(o -> new UserOrder(u, o)));

就会产生嵌套 Optional。这里必须根据函数返回类型选择 mapflatMap

对于三个或更多值,继续嵌套会降低可读性。可以改用明确的校验、专门的组合函数或领域对象,而不是为了“全程链式”强行制造深层 lambda。


6.4 Optional 流:stream()

Optional.stream() 把:

Present(x) -> 一个元素的 Stream
Empty      -> 空 Stream

这在批量处理多个 Optional 时非常有用:

List<Optional<String>> values = List.of(
        Optional.of("Java"),
        Optional.empty(),
        Optional.of("JVM")
);

List<String> presentValues = values.stream()
        .flatMap(Optional::stream)
        .toList();

System.out.println(presentValues); // [Java, JVM]

如果直接使用 map(Optional::get),空元素会抛出 NoSuchElementException

values.stream()
        .map(Optional::get)
        .toList(); // 失败

flatMap(Optional::stream) 的空值语义更准确:空 Optional 不产生元素。


七、Optional 与 null 的边界设计

7.1 推荐作为“可能缺失的返回值”

Optional 最适合用于方法返回值:

Optional<User> findById(long id)

调用方看到签名就知道:

用户可能存在,也可能不存在

与之相比,下面的签名具有歧义:

User findById(long id)

调用者必须阅读文档、实现或测试才能知道返回 null 是否允许。


7.2 不要让 Optional 自身再次为 null

下面的接口破坏了 Optional 的意义:

Optional<User> findUser(long id) {
    return null;
}

调用方原本只需要处理:

Optional.empty

现在还必须处理:

Optional 对象本身为 null

这产生了双重空值状态:

null
Optional.empty()
Optional.of(user)

正确实现应始终返回一个非 null 的 Optional:

Optional<User> findUser(long id) {
    return id == 42
            ? Optional.of(new User(42, "Ada"))
            : Optional.empty();
}

7.3 不要把 Optional 作为方法参数模拟“可选参数”

不推荐:

void sendEmail(Optional<String> subject) {
    // ...
}

调用方仍然可以传入 null

sendEmail(null);

于是方法内部又出现了双重状态:

null
Optional.empty()
Optional.of(value)

更清晰的选择通常是:

void sendEmail(String subject)

由方法内部规定 null 是否允许,或者提供两个有明确语义的重载:

void sendEmail(String recipient)
void sendEmail(String recipient, String subject)

如果参数本身是配置对象,使用一个明确的配置类型通常比堆叠多个 Optional 参数更易维护。


7.4 不要用 Optional 作为实体字段、DTO 字段或集合元素

例如:

class User {
    private Optional<String> nickname;
}

这会让对象生命周期和序列化边界变复杂:

  • 字段初始化可能遗漏,导致字段本身为 null
  • JSON、ORM、反射框架未必按预期处理 Optional
  • 字段实际拥有三种状态:字段为 null、字段为 Optional.empty()、字段有值;
  • Optional 不是通用的数据建模类型。

领域模型中更常见的做法是直接使用可空字段,并在访问方法或服务边界上转换:

class UserEntity {
    private String nickname; // 数据库字段允许为空

    String nicknameOrNull() {
        return nickname;
    }

    Optional<String> nicknameOptional() {
        return Optional.ofNullable(nickname);
    }
}

对于集合,通常使用集合自身的空语义:

List<User> users = List.of(); // 没有用户

而不是:

Optional<List<User>> users;

两者表达的含义不同:

  • List<User> 为空:查询成功,结果数量为零;
  • Optional<List<User>> 为空:整个结果可能缺失,例如数据源未返回或查询对象不存在。

如果业务确实需要区分“未执行”“查询结果为空”“查询失败”,应使用明确的结果类型,而不是仅靠 Optional<List<T>> 承担所有状态。


八、Optional 与异常:正常缺失和故障必须区分

Optional.empty() 适合表示预期中的“没有结果”:

Optional<User> user = repository.findById(id);

用户不存在可能是正常分支。

但数据库连接失败、权限错误、数据格式损坏通常不是“没有用户”,不应被转换为 Optional.empty()

Optional<User> findById(long id) {
    try {
        return Optional.ofNullable(queryDatabase());
    } catch (SQLException e) {
        return Optional.empty(); // 可能错误地吞掉了数据库故障
    }
}

这段代码把两种完全不同的情况合并了:

查询成功,但没有记录
查询失败,无法确认是否有记录

更合理的处理方式是保留故障:

Optional<User> findById(long id) {
    try {
        return Optional.ofNullable(queryDatabase());
    } catch (SQLException e) {
        throw new DataAccessException(e);
    }
}

如果业务必须同时表达成功、缺失和失败,应使用包含状态的结果类型,例如:

sealed interface LookupResult<T>
        permits Found, Missing, Failed {}

record Found<T>(T value) implements LookupResult<T> {}
record Missing<T>() implements LookupResult<T> {}
record Failed<T>(Exception cause) implements LookupResult<T> {}

Optional 只表达“有值/无值”,不表达错误原因、重试信息、权限状态或超时状态。


九、完整示例:从可空仓储到服务调用

下面的程序可以保存为 OptionalDemo.java,使用 Java 25 编译运行:

import java.util.Optional;

public class OptionalDemo {
    public static void main(String[] args) {
        UserService service = new UserService();

        String existing = service.displayName(42);
        String missing = service.displayName(404);

        System.out.println(existing);
        System.out.println(missing);

        String city = service.findCity(42)
                .orElse("unknown");

        System.out.println(city);
    }

    static final class UserService {
        private final UserRepository repository = new UserRepository();

        String displayName(long id) {
            return Optional.ofNullable(repository.findById(id))
                    .map(User::name)
                    .filter(name -> !name.isBlank())
                    .orElse("anonymous");
        }

        Optional<String> findCity(long id) {
            return Optional.ofNullable(repository.findById(id))
                    .flatMap(User::address)
                    .map(Address::city);
        }
    }

    static final class UserRepository {
        User findById(long id) {
            if (id == 42) {
                return new User(
                        42,
                        "Ada",
                        Optional.of(new Address("London"))
                );
            }

            if (id == 43) {
                return new User(
                        43,
                        "Grace",
                        Optional.empty()
                );
            }

            return null;
        }
    }

    record User(long id, String name, Optional<Address> address) {}

    record Address(String city) {}
}

预期输出:

Ada
anonymous
London

执行路径分别是:

displayName(42)

repository.findById(42)
    -> User
Optional.ofNullable(...)
    -> Optional<User>
map(User::name)
    -> Optional["Ada"]
filter(...)
    -> Optional["Ada"]
orElse("anonymous")
    -> "Ada"

displayName(404)

repository.findById(404)
    -> null
Optional.ofNullable(...)
    -> Optional.empty()
map(...)
    -> Optional.empty()
filter(...)
    -> Optional.empty()
orElse("anonymous")
    -> "anonymous"

findCity(42)

Optional<User>
    -> flatMap(User::address)
    -> Optional<Address>
    -> map(Address::city)
    -> Optional<String>

address 本身是 Optional<Address>,因此使用 flatMapAddress::city 返回普通 String,因此使用 map

这个例子还展示了一个重要边界:如果实体字段已经使用 Optional<Address>,服务层就不需要再次用 ofNullable 包装该字段,而应直接组合它。


十、Optional 的异常与副作用传播

Optional 只控制“值是否存在”,不会捕获 lambda 中的异常。

Optional<String> input = Optional.of("not-a-number");

Optional<Integer> number = input.map(Integer::parseInt);

由于 Integer.parseInt 抛出 NumberFormatException,该异常会直接传播:

Optional.empty()

不会自动得到。map 不是异常处理器。

同样,以下操作中的异常也会传播:

  • filter 的谓词;
  • flatMap 的函数;
  • or 的 supplier;
  • orElseGet 的 supplier;
  • orElseThrow 的异常 supplier。

因此,如果解析失败本来是正常业务状态,需要显式转换:

static Optional<Integer> parseInt(String text) {
    try {
        return Optional.of(Integer.parseInt(text));
    } catch (NumberFormatException e) {
        return Optional.empty();
    }
}

调用:

Optional<Integer> result =
        Optional.of("123").flatMap(OptionalDemo::parseInt);

这里 parseInt 返回 Optional<Integer>,所以使用 flatMap,而不是 map

但不能盲目把所有异常都转换为空。格式不合法可以是“解析失败”,数据库连接断开则通常应作为故障传播。错误分类必须由业务语义决定。


十一、性能:Optional 到底贵在哪里

11.1 Optional 是对象,不是语言级可空标记

Optional<T> 是一个引用对象包装器。对引用类型来说,表达有值状态通常需要一个 Optional 实例;表达空状态可能使用实现中的共享空对象,但共享行为不应作为规范契约依赖。

因此,下面的代码可能创建包装对象:

Optional<String> value = Optional.of(name);

如果在高频循环中为每个元素创建 Optional,可能增加:

  • 对象分配;
  • 垃圾回收压力;
  • 泛型引用层级;
  • 调试和性能分析复杂度。

但是,不能仅凭“Optional 是对象”就断言所有场景必然有明显性能问题。现代 HotSpot 可能通过逃逸分析和标量替换消除某些短生命周期对象,实际结果取决于:

  • JIT 是否编译了该代码;
  • 对象是否逃逸到方法外;
  • 调用是否被内联;
  • 热点路径的数据规模;
  • GC 和 CPU 状态。

这属于常见实现行为,不是 Optional API 的规范保证。需要性能结论时,应使用代表性数据和基准测试验证,而不能引用未经验证的固定百分比。


11.2 引用类型 Optional 与基本类型 Optional

下面的代码会产生装箱:

Optional<Integer> count = Optional.of(42);

如果结果本身是可选的基本数值,使用专用类型可以避免把 int 先装箱成 Integer

OptionalInt count = OptionalInt.of(42);

if (count.isPresent()) {
    System.out.println(count.getAsInt());
}

对应 API 包括:

OptionalInt
OptionalLong
OptionalDouble

它们不是 Optional<Integer> 的子类型,方法名也不同:

count.getAsInt();

而不是:

count.get();

这类类型适合在数值密集型路径中使用,但不要为了“避免一次装箱”而牺牲接口可读性。边界层、普通业务代码和低频路径通常应优先保持语义清晰。


11.3 不要通过 Optional 判断性能

以下写法不能证明哪一种一定更快:

value.orElse(defaultValue);
value.orElseGet(() -> defaultValue);

如果 defaultValue 已经是一个简单现成引用,orElse 更直观;如果默认值要计算,orElseGet 改变的是求值时机,并可能避免计算。性能差异应从实际执行路径分析,而不是把所有 Optional 操作都归类为“慢”。

真正容易造成严重成本的通常是错误的副作用:

optional.orElse(queryDatabase());

这不仅可能创建对象,还可能在主值存在时执行数据库查询。这里首先是语义错误,其次才是性能问题。


十二、常见错误用法及失败表现

12.1 Optional.of 包装可空值

Optional<String> value = Optional.of(getNullableValue());

失败表现:

getNullableValue() 返回 null 时抛出 NullPointerException

如果 null 是正常状态:

Optional<String> value =
        Optional.ofNullable(getNullableValue());

如果 null 代表程序错误,则保留 of,让错误尽早暴露。关键是选择要反映语义,而不是机械替换。


12.2 使用 isPresent() 后在别处调用 get()

if (user.isPresent()) {
    logUser();
}

User value = user.get();

检查和取值不在同一个控制流保证范围内,get() 仍可能失败。应在同一分支内使用,或改为:

user.ifPresent(this::logUser);

或者:

User value = user.orElseThrow(UserNotFoundException::new);

12.3 用 map 造成嵌套 Optional

Optional<Optional<Address>> result =
        findUser(id).map(this::findAddress);

当函数已经返回 Optional<R> 时,应使用:

Optional<Address> result =
        findUser(id).flatMap(this::findAddress);

诊断方法很简单:查看 lambda 或方法引用的返回类型。如果返回:

R

使用 map;如果返回:

Optional<R>

使用 flatMap


12.4 把 orElse 当作惰性分支

String value = optional.orElse(expensiveOperation());

即使 Optional 有值,expensiveOperation() 也会执行。改为:

String value = optional.orElseGet(this::expensiveOperation);

如果 expensiveOperation() 抛异常,错误表现通常是:调用方明明有主值,却仍然收到了默认值计算阶段的异常。


12.5 对 Optional 调用 get() 只是为了继续处理

String value = optional.get().trim().toLowerCase();

这会把空值检查推迟到一个不明确的位置。更安全的写法是:

Optional<String> normalized = optional
        .map(String::trim)
        .map(String::toLowerCase);

如果最终必须得到字符串:

String normalized = optional
        .map(String::trim)
        .map(String::toLowerCase)
        .orElse("unknown");

12.6 在流中用 Optional 表示每个元素的过滤结果

List<Optional<String>> results = names.stream()
        .map(this::normalizeIfValid)
        .toList();

这种设计可能合理,例如调用方确实需要知道每个输入对应的成功或缺失状态。但如果只是想保留有效值,应直接用 filter

List<String> results = names.stream()
        .map(String::trim)
        .filter(name -> !name.isEmpty())
        .toList();

如果已经得到 Stream<Optional<T>>,再用:

.flatMap(Optional::stream)

消除空元素。


十三、Optional 的类型推断边界

泛型推断有时需要显式指定类型,尤其是在 empty()orElse 或复杂 lambda 组合中:

Optional<String> value = Optional.empty();

目标类型可以帮助编译器推断 String

但在复杂表达式中,显式类型参数更清楚:

Optional<String> value =
        Optional.<String>empty()
                .or(() -> Optional.of("fallback"));

方法引用也必须满足目标函数类型。例如:

Optional<Integer> number =
        Optional.of("42").flatMap(OptionalDemo::parseInt);

parseInt 必须返回 Optional<Integer>。如果它返回 Integer,应使用 map

Optional<Integer> number =
        Optional.of("42").map(Integer::parseInt);

选择 mapflatMap 的根本依据是返回类型,而不是链条长短。


十四、Optional 的相等性、哈希和身份

Optionalequals 比较其逻辑状态和值:

System.out.println(
        Optional.of("Java").equals(Optional.of("Java"))
); // true

System.out.println(
        Optional.empty().equals(Optional.empty())
); // true

hashCode 也基于所包含的值,空 Optional 的哈希值为零。

这说明 Optional 适合进行值语义比较:

Optional<String> expected = Optional.of("Java");
Optional<String> actual = Optional.of("Java");

assert expected.equals(actual);

但不应将 Optional 当作需要身份管理的锁、缓存键对象或可变状态容器。它本身不可变,且 value-based 语义不保证对象身份稳定。

另外,Optional 不应被误解为通用序列化载体。它不是普通领域实体字段的替代品,也不能假设所有 ORM、JSON 库或远程协议都自然支持它。跨边界的数据结构应根据具体框架的契约验证,而不是依据 Optional 在 Java 代码中的表现推断。


十五、一个可执行的选择模型

面对一个可能为空的值,可以按以下因果顺序判断:

值的来源可能返回 null

在边界转换:

Optional.ofNullable(source());

已有值需要转换成普通结果

使用:

optional.map(function);

已有值需要调用另一个返回 Optional 的函数

使用:

optional.flatMap(function);

只在满足条件时保留

使用:

optional.filter(predicate);

当前为空时尝试另一个 Optional 来源

使用:

optional.or(alternativeSupplier);

最终需要普通值,默认值已存在

使用:

optional.orElse(defaultValue);

最终需要普通值,默认值需要延迟计算

使用:

optional.orElseGet(supplier);

空值说明业务前置条件失败

使用:

optional.orElseThrow(exceptionSupplier);

只需要执行动作,不需要构造返回值

使用:

optional.ifPresent(action);

需要同时处理存在和缺失两个动作

使用:

optional.ifPresentOrElse(action, emptyAction);

这不是语法替换表,而是由每个操作的类型和求值规则推导出来的:

  • map 保持一层 Optional;
  • flatMap 消除一层 Optional;
  • or 组合 Optional 来源;
  • orElse* 离开 Optional 世界,得到普通值;
  • ifPresent* 离开 Optional 世界,执行副作用动作。

十六、最终边界:何时使用,何时不用

适合使用 Optional 的核心场景是:

Optional<User> findById(long id)

它把“缺失是合法结果”写入返回类型,并迫使调用方选择缺失处理方式。

不适合滥用的场景包括:

Optional<User> parameter
Optional<User> field
Optional<Optional<User>>
Optional<List<User>> // 只是为了表达普通空集合

还应避免将异常、超时、权限失败等故障简单折叠成 Optional.empty()Optional 的状态空间只有:

Empty
Present(value)

当业务需要表达更多状态时,应使用更能承载这些状态的类型。

在性能敏感路径中,先确认 Optional 是否真的出现在热点、是否发生对象逃逸、是否引入装箱,再决定是否调整接口。不要在没有测量的情况下凭固定数字断言性能损失;也不要为了避免包装对象,把清晰的缺失语义重新改回隐含的 null

Optional 的正确定位是:在方法返回边界上,用类型显式表示“结果可能不存在”,并通过 mapflatMapfilteror 和终止操作构造有规则的空值传播流程。


系列导航与关联阅读

官方资料

本文依据 Java、Spring 与相关项目官方文档重新梳理;正文、示例与生产清单由 WR BLOG 编写。