Optional
概述
Optional 是 JDK 8 引入的容器类(java.util.Optional),用于表示「可能有值,可能没有值」的单一结果。它不替代所有 null,而是为方法返回值提供一种显式表达「无值」的方式,从而减少直接使用 null 导致的空指针异常(NPE),并让调用方必须显式处理「无值」情况。
通俗理解:一个方法返回 Optional<User>,就是在告诉调用方「我可能给你一个 User,也可能没有」;调用方通过 isPresent()、orElse()、ifPresent() 等方式安全地处理这两种情况,而不是拿到一个可能为 null 的引用再去判空。
前置建议
已掌握 泛型(如 Optional<T>)和 Lambda 表达式。Optional 常与 Stream API 的 findFirst()、findAny() 等配合使用。
为什么需要 Optional
在 JDK 8 之前,方法若可能「没有结果」,通常返回 null,调用方容易忘记判空而导致 NPE:
// 可能返回 null,调用方若忘记判空就会 NPE
public User findUserById(long id) {
// ...
return user; // 或 return null;
}
// 调用处:若 getAddress() 也可能为 null,链式调用易 NPE
String city = findUserById(1).getAddress().getCity(); // 危险!使用 Optional 后,无值被显式建模,编译器无法帮你强制处理,但 API 设计上会促使调用方用 orElse、ifPresent 等方式处理空情况:
public Optional<User> findUserById(long id) {
// ...
return Optional.ofNullable(user);
}
// 调用处:必须考虑「无用户」的情况
Optional<User> opt = findUserById(1);
String city = opt
.map(User::getAddress)
.map(Address::getCity)
.orElse("未知");创建 Optional
| 方法 | 说明 |
|---|---|
Optional.of(value) | 创建包含非 null 值的 Optional;若传入 null 会立刻抛出 NullPointerException |
Optional.ofNullable(value) | 若 value 为 null 则得到空 Optional,否则包含该值(最常用) |
Optional.empty() | 得到空的 Optional 实例 |
Optional<String> a = Optional.of("hello"); // 有值
Optional<String> b = Optional.ofNullable(null); // 空
Optional<String> c = Optional.empty(); // 空
// Optional.of(null) 会抛出 NPE,不要对可能为 null 的变量使用 of提示
方法返回值可能为 null 时,用 Optional.ofNullable(result) 包装;确定不为 null 时用 Optional.of(result)。若本来就没有值,直接返回 Optional.empty()。
基本用法:判断与取值
判断是否有值
Optional<String> opt = Optional.ofNullable(getMessage());
if (opt.isPresent()) {
System.out.println(opt.get());
}isPresent():有值返回 true,空则 false。get():有值时返回该值;空时抛出NoSuchElementException,因此应先判空或改用下面更安全的 API。
注意
尽量避免「先 isPresent() 再 get()」的写法,更推荐用 orElse、ifPresent、orElseThrow 等,减少漏判或冗长代码。
版本说明
Optional.isEmpty()(与 isPresent() 相反)自 JDK 11 提供;JDK 8 中请用 !optional.isPresent()。
安全取值:orElse、orElseGet、orElseThrow
无值时的默认值或替代行为:
Optional<String> opt = Optional.ofNullable(getMessage());
// 无值时使用默认值(默认值会先求值)
String s1 = opt.orElse("default");
// 无值时用 Lambda 计算默认值(仅无值时才调用,适合昂贵计算)
String s2 = opt.orElseGet(() -> computeDefault());
// 无值时抛出异常(常用于「必须有值」的业务场景)
String s3 = opt.orElseThrow(() -> new IllegalStateException("缺少必填项"));| 方法 | 行为 |
|---|---|
orElse(T other) | 无值时返回 other;other 会先求值 |
orElseGet(Supplier<? extends T>) | 无值时调用 Supplier 得到结果;无值才调用,适合昂贵计算 |
orElseThrow(Supplier<? extends X>) | 无值时抛出异常(JDK 10+ 有无参重载 orElseThrow(),无值抛 NoSuchElementException) |
条件执行:ifPresent 与 ifPresentOrElse
有值时才执行一段逻辑,无需手写 if (opt.isPresent()) { ... }:
Optional<String> opt = Optional.ofNullable(getMessage());
// 有值时执行 Lambda,无值什么都不做
opt.ifPresent(s -> System.out.println("消息: " + s));
// JDK 9+:有值执行第一个 Lambda,无值执行第二个
opt.ifPresentOrElse(
s -> System.out.println("消息: " + s),
() -> System.out.println("无消息")
);链式转换:map 与 flatMap
在「有值」的前提下做转换,无值则整条链仍为空 Optional:
Optional<User> userOpt = findUserById(1);
// 从 Optional<User> 得到 Optional<String>(例如用户名的长度)
Optional<Integer> lenOpt = userOpt.map(User::getName).map(String::length);
// 若 getUserName() 返回的是 Optional<String>,用 flatMap 避免 Optional<Optional<String>>
Optional<String> nameOpt = userOpt.flatMap(User::getNickName);map(Function):有值时用函数做一次转换,得到Optional<U>;无值则得到Optional.empty()。flatMap(Function<T, Optional<U>>):有值时用函数得到另一个 Optional,结果被「压平」为Optional<U>,避免嵌套Optional<Optional<U>>。
示例:组合多个可能为空的步骤:
// 获取用户所在城市,任一步无值则结果为 empty
Optional<String> cityOpt = findUserById(1)
.map(User::getAddress)
.map(Address::getCity);
String city = cityOpt.orElse("未知");过滤:filter
有值且满足条件才保留,否则变为空 Optional:
Optional<User> opt = findUserById(1);
Optional<User> adult = opt.filter(u -> u.getAge() >= 18);
adult.ifPresent(u -> System.out.println(u.getName()));完整示例
示例 1:根据 ID 查用户并打印名称
public void printUserName(long id) {
findUserById(id)
.map(User::getName)
.ifPresent(name -> System.out.println("用户: " + name));
}示例 2:无值时使用默认值或抛异常
// 配置项:无则用默认值
String timeout = Optional.ofNullable(System.getProperty("app.timeout"))
.orElse("30");
// 必填项:无则抛业务异常
String id = Optional.ofNullable(request.getParameter("id"))
.orElseThrow(() -> new IllegalArgumentException("缺少参数 id"));示例 3:与 Stream 结合(findFirst / findAny)
List<User> users = getUsers();
Optional<User> first = users.stream()
.filter(u -> u.getAge() >= 18)
.findFirst();
first.ifPresent(u -> System.out.println(u.getName()));注意事项
不要用 Optional 做成员变量
Optional 的设计意图是方法返回值,而不是字段。将 Optional 作为类的成员变量会增加无谓的包装、序列化与语义歧义,不推荐。字段「可能为 null」时,保持为可空类型,在 getter 中如需可返回 Optional.ofNullable(field)。
不要用 Optional 替代所有 null
方法内部实现、局部变量、参数等不必强行改为 Optional;集合「可能为空」应用空集合而非 Optional<List<T>>。Optional 用在可能无单一结果的返回值上最能体现价值。
避免无判空就调用 get()
空 Optional 上调用 get() 会抛出 NoSuchElementException。优先使用 orElse、orElseThrow、ifPresent、map 等 API,避免「先 isPresent 再 get」或直接 get。
提示
链式调用时,任一步得到空 Optional,后续 map/filter 都会跳过,最终得到 empty,用 orElse/orElseThrow 收尾即可,代码简洁且安全。
相关链接
- Lambda 表达式 — Optional 常与 Lambda、方法引用配合
- Stream API —
findFirst()、findAny()等返回 Optional - 泛型基础 — 理解
Optional<T>的类型 - Oracle Java 文档 - Optional