Optional 与空指针:让「可能没有值」显式化
更新时间:2026-09-20。NullPointerException 是 Java 最常见运行时异常。
Optional<T>用类型系统把「值可能缺失」写进签名,逼调用方正视缺失分支。它不是用来「替代所有 null」,而是明确返回语义。
一、三种创建方式
java
Optional<String> a = Optional.of("x"); // 参数不能为 null,否则 NPE(确定非空时用)
Optional<String> b = Optional.ofNullable(get()); // 可空,最常用
Optional<String> c = Optional.empty(); // 显式空| 方法 | 行为 | 注意 |
|---|---|---|
get() | 有值返回,无值抛 NoSuchElementException | 不要在 isPresent 前裸调 |
isPresent() | 是否有值 | 配合处理,但别写成 null 判断换皮 |
ifPresent(v->...) | 有值才执行消费函数 | 推荐 |
orElse(x) | 无值给默认值 | x 总会被求值 |
orElseGet(Supplier) | 无值才调 Supplier | 有开销的默认值用它(惰性) |
orElseThrow() | 无值抛异常 | 缺失即非法场景 |
map/flatMap | 有值才转换并继续包裹 | 链式防空 |
二、推荐的链式写法
java
// 老式:多层 null 判断
String city = null;
if (user != null && user.getAddr() != null) {
city = user.getAddr().getCity();
}
// Optional 风格:任一环为空都安全短路
String c = Optional.ofNullable(user)
.map(User::getAddr)
.map(Address::getCity)
.orElse("未知"); // 全链无空才取到 city,否则默认
// 默认值构造昂贵时用 orElseGet,避免每次都 new
Order o = findById(id).orElseGet(() -> new Order());
// 缺失属于错误:直接抛
Config cfg = load().orElseThrow(() -> new IllegalStateException("缺少配置"));
三、关键区别:orElse 与 orElseGet
java
Optional<String> opt = Optional.of("hi");
opt.orElse(expensive()); // expensive() 即使有值也会执行!
opt.orElseGet(() -> expensive()); // 只有空时才执行默认值是常量用 orElse;默认值要查库/new 对象/计算,用 orElseGet 省掉无谓开销。
坑
- 把 Optional 当字段或方法参数:它设计为返回值,不可变但有包装开销,字段/集合元素/入参仍直接用普通类型,可空字段用 null + 文档或拆成两个方法。
opt.get()不先判断:和直接用 null 一样会抛异常,等于没解决问题;取值优先 orElse/orElseThrow。- 用 isPresent + get 复刻 if-null:失去声明式价值,直接
map().orElse()或 ifPresent 更简洁。 - 集合为空别用
Optional<List>:集合本身返回空集合即可,无需再包一层 Optional。 - flatMap 与 map 混用:当转换函数本身返回 Optional 时要用
flatMap,否则会套成Optional<Optional<T>>。
总结
Optional 是「可能缺失的值」的容器,用类型签名强迫调用方处理空分支:确定非空用 of、可空用 ofNullable,取值按场景选 orElse(默认常量、立即求值)、orElseGet(惰性、有开销的默认值)或 orElseThrow,配合 map 链安全穿透多层可能为空的对象;但它只应作为返回值,不要做字段/入参/集合元素,也不要用 isPresent+get 把它写成另一种 null 判断。