Optional 增强(JDK 9 / 10 / 11)
飞翔科技的监控系统告警了:线上出现了 NullPointerException。小崔急忙打开日志追踪,发现是一段 Optional 使用不当的代码。
Optional<Employee> emp = employeeDao.findById(id); if (emp.isPresent()) { sendNotification(emp.get()); } else { logError("员工不存在: " + id); }白歌在事故复盘会上说:"这段代码有 JDK 8 的壳,没有 JDK 8 的魂。
isPresent() + get()是反模式——但真正的问题在于:即使你想做得对,JDK 8 的 Optional 确实缺少一些关键方法。JDK 9-11 就是来补这些窟窿的。"
定义表:JDK 9-11 新增方法一览
| 方法 | 版本 | 签名 | 功能 | 等效 JDK 8 写法 |
|---|---|---|---|---|
ifPresentOrElse | JDK 9 | (Consumer<T>, Runnable) | 值存在执行 Consumer,不存在执行 Runnable | if (opt.isPresent()) { ... } else { ... } |
or | JDK 9 | (Supplier<Optional<T>>) | 值存在返回自身,否则返回备选 Optional 的结果 | opt.isPresent() ? opt : fallback.get() |
stream | JDK 9 | () | 将 Optional 转为 0 或 1 个元素的 Stream | opt.isPresent() ? Stream.of(opt.get()) : Stream.empty() |
orElseThrow() | JDK 10 | () | 值存在返回,否则抛出 NoSuchElementException | opt.orElseThrow(() -> new NoSuchElementException()) |
isEmpty | JDK 11 | () | 判断值是否为空 | !opt.isPresent() |
Mermaid 流程图:JDK 9-11 Optional 方法全景
ifPresentOrElse — 告别 isPresent + get
3.1 JDK 8 的痛点 vs JDK 9 的解法
import java.util.Optional;
public class IfPresentOrElseDemo {
public static void main(String[] args) {
// === 场景:飞翔科技员工查询 ===
Optional<String> emp1 = findEmployee(1001); // 存在
Optional<String> emp2 = findEmployee(9999); // 不存在
// --- JDK 8 写法:if-else + isPresent + get(繁琐) ---
if (emp1.isPresent()) {
System.out.println("找到员工: " + emp1.get());
} else {
System.out.println("员工不存在");
}
// --- JDK 9 写法:一行搞定,优雅且无 get() ---
emp1.ifPresentOrElse(
name -> System.out.println("找到员工: " + name),
() -> System.out.println("员工不存在")
);
emp2.ifPresentOrElse(
name -> System.out.println("找到员工: " + name),
() -> System.out.println("员工不存在(日志已记录)")
);
}
static Optional<String> findEmployee(int id) {
if (id == 1001) return Optional.of("大翔");
if (id == 1002) return Optional.of("白歌");
return Optional.empty();
}
}
输出:
找到员工: 大翔
找到员工: 大翔
员工不存在(日志已记录)
3.2 实战:缓存查询的双分支处理
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class CacheQueryDemo {
// 模拟缓存和数据库
static Map<Integer, String> cache = new HashMap<>();
static {
cache.put(1001, "大翔");
cache.put(1002, "白歌");
}
public static void main(String[] args) {
queryEmployee(1001);
queryEmployee(1003);
}
static void queryEmployee(int id) {
System.out.println("--- 查询员工 " + id + " ---");
Optional.ofNullable(cache.get(id))
.ifPresentOrElse(
// 缓存命中:快速返回
name -> System.out.println("缓存命中: " + name),
// 缓存未命中:查数据库 + 记录日志
() -> {
System.out.println("缓存未命中,查询数据库...");
String dbResult = queryDatabase(id);
if (dbResult != null) {
cache.put(id, dbResult); // 回填缓存
}
System.out.println("最终结果: " + dbResult);
}
);
}
static String queryDatabase(int id) {
// 模拟数据库,id=1003 存在,其他不存在
return id == 1003 ? "孔蓝" : null;
}
}
输出:
--- 查询员工 1001 ---
缓存命中: 大翔
--- 查询员工 1003 ---
缓存未命中,查询数据库...
最终结果: 孔蓝
or — 链式备选 Optional
4.1 JDK 8 的备选困境
import java.util.Optional;
public class OrDemo {
public static void main(String[] args) {
// === 三级缓存查询:L1 → L2 → 数据库 ===
Optional<String> result1 = queryL1Cache(1001);
Optional<String> result2 = queryL1Cache(9999);
// --- JDK 8 写法:层层嵌套 orElseGet ---
// 问题:每一层都返回 T 而不是 Optional<T>,丢失了空值语义
String emp1 = result1.orElseGet(() ->
queryL2Cache(1001).orElseGet(() ->
queryDatabase(1001).orElse("未知员工")
)
);
// --- JDK 9 写法:链式 or(),保持 Optional 管道 ---
Optional<String> emp2 = result2
.or(() -> queryL2Cache(9999))
.or(() -> queryDatabase(9999));
System.out.println("JDK 8方式: " + emp1);
System.out.println("JDK 9方式: " + emp2.orElse("未知员工"));
}
static Optional<String> queryL1Cache(int id) {
return id == 1001 ? Optional.of("大翔(L1)") : Optional.empty();
}
static Optional<String> queryL2Cache(int id) {
return id == 1001 ? Optional.of("大翔(L2)") : Optional.empty();
}
static Optional<String> queryDatabase(int id) {
return Optional.empty();
}
}
输出:
JDK 8方式: 大翔(L1)
JDK 9方式: 未知员工
4.2 or vs orElseGet 的本质区别
import java.util.Optional;
public class OrVsOrElseGetDemo {
public static void main(String[] args) {
Optional<String> present = Optional.of("飞翔科技");
Optional<String> empty = Optional.empty();
// === orElseGet:返回 T(拆箱) ===
String val1 = empty.orElseGet(() -> {
System.out.println("orElseGet 执行了");
return "默认值";
});
System.out.println("orElseGet 结果: " + val1);
System.out.println("类型: " + val1.getClass().getSimpleName());
// === or:返回 Optional<T>(保持装箱) ===
Optional<String> val2 = empty.or(() -> {
System.out.println("or() 执行了");
return Optional.of("备选值");
});
System.out.println("\nor() 结果: " + val2);
System.out.println("类型: " + val2.getClass().getSimpleName());
// 还能继续链式操作!
String finalResult = val2.map(String::toUpperCase).orElse("最终默认");
System.out.println("链式转换后: " + finalResult);
}
}
输出:
orElseGet 执行了
orElseGet 结果: 默认值
类型: String
or() 执行了
or() 结果: Optional[备选值]
类型: Optional
链式转换后: 备选值
核心差异:
orElseGet返回"值本身"(终结操作),or()返回"Optional"(继续链式)。当你有多级备选时,or()让管道不中断。
stream() — Optional 到 Stream 的桥梁
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class OptionalStreamDemo {
public static void main(String[] args) {
// === 场景:批量查询员工,只保留有效结果 ===
List<Integer> ids = List.of(1001, 1002, 9998, 1003, 9999);
// --- JDK 8 写法:先 filter 再 map(get) ---
List<String> result1 = ids.stream()
.map(OptionalStreamDemo::findEmployee)
.filter(Optional::isPresent)
.map(Optional::get) // ⚠️ get() 是不安全的信号
.collect(Collectors.toList());
System.out.println("JDK 8: " + result1);
// --- JDK 9 写法:flatMap(Optional::stream) ---
List<String> result2 = ids.stream()
.map(OptionalStreamDemo::findEmployee)
.flatMap(Optional::stream) // ✅ 优雅!自动跳过 empty
.collect(Collectors.toList());
System.out.println("JDK 9: " + result2);
// === 原理解析:Optional.stream() 做了什么 ===
Optional<String> present = Optional.of("大翔");
Optional<String> empty = Optional.empty();
Stream<String> s1 = present.stream(); // 流中有1个元素:"大翔"
Stream<String> s2 = empty.stream(); // 空流
System.out.println("\npresent.stream().count() = " + s1.count()); // 1
System.out.println("empty.stream().count() = " + s2.count()); // 0
}
static Optional<String> findEmployee(int id) {
return switch (id) {
case 1001 -> Optional.of("大翔");
case 1002 -> Optional.of("白歌");
case 1003 -> Optional.of("孔蓝");
default -> Optional.empty();
};
}
}
输出:
JDK 8: [大翔, 白歌, 孔蓝]
JDK 9: [大翔, 白歌, 孔蓝]
present.stream().count() = 1
empty.stream().count() = 0
orElseThrow() 无参版本(JDK 10)
import java.util.Optional;
public class OrElseThrowDemo {
public static void main(String[] args) {
// --- JDK 8:必须提供异常 Supplier ---
Optional<String> opt1 = Optional.empty();
// opt1.orElseThrow(() -> new IllegalArgumentException("值不存在"));
// --- JDK 10:无参版本,默认抛 NoSuchElementException ---
Optional<String> opt2 = Optional.of("飞翔科技");
String result = opt2.orElseThrow(); // 简洁!
System.out.println("结果: " + result);
// 有值时与 get() 等价,但语义更清晰
// get() 在 empty 时不友好,orElseThrow() 明确表达了"期望非空"
// --- 演示异常 ---
try {
Optional<String> empty = Optional.empty();
empty.orElseThrow(); // → NoSuchElementException: No value present
} catch (Exception e) {
System.out.println("异常类型: " + e.getClass().getSimpleName());
System.out.println("异常消息: " + e.getMessage());
}
}
}
输出:
结果: 飞翔科技
异常类型: NoSuchElementException
异常消息: No value present
isEmpty() — 更可读的空判断(JDK 11)
import java.util.Optional;
public class IsEmptyDemo {
public static void main(String[] args) {
Optional<String> present = Optional.of("飞翔科技");
Optional<String> empty = Optional.empty();
// --- JDK 8:只能用 !isPresent() ---
System.out.println("JDK 8风格:");
if (!present.isPresent()) System.out.println("不存在");
if (!empty.isPresent()) System.out.println("不存在");
// --- JDK 11:isEmpty() 更自然 ---
System.out.println("\nJDK 11风格:");
if (present.isEmpty()) System.out.println("不存在");
if (empty.isEmpty()) System.out.println("不存在");
// === 实际场景:参数校验 ===
validateParam(Optional.of("大翔"));
validateParam(Optional.empty());
}
static void validateParam(Optional<String> param) {
if (param.isEmpty()) {
System.out.println("❌ 参数缺失");
} else {
System.out.println("✅ 参数有效: " + param.get());
}
}
}
输出:
JDK 8风格:
不存在
JDK 11风格:
不存在
✅ 参数有效: 大翔
❌ 参数缺失
易错场景
8.1 ifPresentOrElse 的 Runnable 中不能抛受检异常
import java.util.Optional;
public class IfPresentOrElsePitfall {
public static void main(String[] args) {
Optional<String> empty = Optional.empty();
// ❌ Runnable 的 run() 方法签名没有 throws
// empty.ifPresentOrElse(
// System.out::println,
// () -> { throw new Exception("错误"); } // 编译错误
// );
// ✅ 正确:在 Runnable 内部 try-catch 处理
empty.ifPresentOrElse(
System.out::println,
() -> {
try {
throw new Exception("受检异常");
} catch (Exception e) {
System.err.println("捕获: " + e.getMessage());
}
}
);
}
}
8.2 or() 返回的 Optional 可能仍是 empty
import java.util.Optional;
public class OrChainPitfall {
public static void main(String[] args) {
var result = Optional.<String>empty()
.or(() -> Optional.empty()) // 仍是 empty
.or(() -> Optional.empty()); // 仍是 empty
// or() 链条全部返回 empty 时,最终仍是 Optional.empty
System.out.println("全部 empty 后: " + result.isEmpty());
// 不会自动抛异常,必须显式 orElseThrow() 或在末尾 orElse()
System.out.println("默认值: " + result.orElse("兜底值"));
}
}
面试考点
问题一:"or() 和 orElseGet() 的区别?"
orElseGet(Supplier<T>)返回类型T,是终结操作,拆箱出实际值。or(Supplier<Optional<T>>)返回Optional<T>,保持包装状态,支持继续链式调用(如map、filter、再or)。多级备选时or()更合适。
问题二:"Optional.stream() 的实际用途?"
将 Optional 转成 0 或 1 个元素的 Stream。最大用途是在流管道中的
flatMap(Optional::stream)一步完成"过滤 empty + 提取值",替代filter(Optional::isPresent).map(Optional::get)的组合。
问题三:"orElseThrow() 无参版本和 get() 的区别?"
行为上几乎等价:有值返回值,无值抛异常。但语义不同:
get()是"我假设这里有值"(不安全),orElseThrow()无参版明确说"没值就是异常"(语义清晰)。JDK 10 的发行说明推荐用orElseThrow()替代get()。
白歌在团队 Wiki 写了三条 Optional 铁律,至今挂在飞翔科技的代码规范页面上:
- 不用
isPresent() + get()组合——用ifPresentOrElse、orElseGet、orElseThrow- 不用
filter(isPresent).map(get)——用flatMap(Optional::stream)- 不用
get()——用orElseThrow()小崔把这三条打印在了工位上。线上再没出过 Optional 相关的 NPE。