Java 中 Optional 的用法详解
在 Java 中,NullPointerException
(空指针异常)是最常见的运行时异常之一。为了优雅地处理 “空值” 场景、避免直接使用 null
带来的风险,Java 8 引入了 java.util.Optional
类 —— 它本质是一个 “容器”,可以存储一个非空值(value
)或表示 “无值”(empty
),通过封装空值判断逻辑,让代码更简洁、可读性更高。
先看一个传统处理空值的痛点场景,如果要获取用户的地址信息,且用户、地址可能为 null
,传统写法需要层层非空判断:
User user = getUserById(1);
String city = null;
if (user != null) {
Address address = user.getAddress();
if (address != null) {
city = address.getCity();
}
}
System.out.println(city); // 可能为 null
用 Optional
优化后,无需手动写 if (xxx != null)
,通过链式调用即可处理空值:
String city = Optional.ofNullable(getUserById(1))
.map(User::getAddress)
.map(Address::getCity)
.orElse("默认城市");
System.out.println(city);
可见 Optional
的核心价值是:统一封装空值判断逻辑,避免嵌套判空,减少 NPE 风险。
创建Optional对象
方法 | 作用 | 注意事项 |
---|---|---|
Optional.of(T value) | 包装一个非空值 | 若 value 为 null ,直接抛 NullPointerException |
Optional.ofNullable(T value) | 包装一个可能为 null的值 | 若 value 为 null ,返回 Optional.empty() |
Optional.empty() | 创建一个 “无值” 的 Optional 实例 | 等价于包装了 null ,但不会直接暴露 null |
// 1. of:包装非空值(value 不能为 null)
Optional<String> nonNullOpt = Optional.of("Hello");
String nullOpt = Optional.of(null); // 直接抛 NPE
// 2. ofNullable:包装可能为 null 的值(安全)
String maybeNull = null;
Optional<String> maybeNullOpt = Optional.ofNullable(maybeNull); // 结果是 empty
// 3. empty:创建无值实例
Optional<String> emptyOpt = Optional.empty(); // 结果是 empty
获取Optional中的值
get():直接获取值(易报错空指针)
orElse(T other):null返回默认值
Optional<String> opt1 = Optional.of("Java");
String result1 = opt1.orElse("Python");
orElseGet(Supplier<? extends T> supplier):null动态生成默认值
- 作用:与
orElse
类似,但默认值由Supplier
函数动态生成(懒加载)。 - 优势:仅当
Optional
为empty
时,才执行supplier
逻辑,避免资源浪费(如数据库查询、复杂计算)。
orElseThrow(Supplier<? extends X> exceptionSupplier):null抛出异常
若为 empty
,抛出 supplier
生成的自定义异常,替代默认的 NoSuchElementException
。
Optional<User> userOpt = Optional.empty();
User user = userOpt.orElseThrow(() -> new RuntimeException("用户不存在"));
过滤Optional中的值
filter(Predicate<? super T> predicate)
class User {
private String name;
private int age;
// 构造器、getter
}
// 模拟查询到一个用户(年龄 17,未成年)
User minorUser = new User("Tom", 17);
Optional<User> userOpt = Optional.ofNullable(minorUser);
// 过滤:只保留年龄 >= 18 的用户
Optional<User> adultUserOpt = userOpt.filter(user -> user.getAge() >= 18);
// 结果:adultUserOpt 是 empty(17 < 18,不满足条件)
String result = adultUserOpt.map(User::getName).orElse("无成年用户");
System.out.println(result); // 输出:无成年用户
转换Optional中的值
map(Function<? super T, ? extends U> mapper)
class Address {
private String city;
// 构造器、getter
}
// 模拟用户有地址,地址有城市
User user = new User("Bob", 22, new Address("北京"));
Optional<User> userOpt = Optional.of(user);
// 链式转换:User -> Address -> String(城市)
String city = userOpt.map(User::getAddress) // 先提取 Address,返回 Optional<Address>
.map(Address::getCity) // 再提取 City,返回 Optional<String>
.orElse("未知城市");
System.out.println(city); // 输出:北京
处理嵌套Optional
如果 map
函数的返回值本身就是 Optional
,会导致 “嵌套 Optional”(如 Optional<Optional<String>>
),此时需要用 flatMap
扁平化处理:
map
:会保留嵌套的Optional
;flatMap
:会将嵌套的Optional
拆成一层(直接返回内层Optional
)。
class User {
private Optional<Address> address; // 嵌套 Optional
// 构造器、getter
}
// 模拟用户有地址(返回 Optional<Address>)
Address address = new Address("上海");
User user = new User(Optional.of(address));
Optional<User> userOpt = Optional.of(user);
// 若用 map:会得到 Optional<Optional<Address>>(嵌套)
Optional<Optional<Address>> nestedOpt = userOpt.map(User::getAddress);
// 用 flatMap:扁平化,得到 Optional<Address>(一层)
Optional<Address> flatOpt = userOpt.flatMap(User::getAddress);
// 继续提取城市
String city = flatOpt.map(Address::getCity).orElse("未知城市");
System.out.println(city); // 输出:上海
判断值是否存在
isPresent()
:判断是否有值(返回 boolean)
Optional<String> opt = Optional.of("Java");
if (opt.isPresent()) {
System.out.println("有值:" + opt.get()); // 输出:有值:Java
}
尽量避免用 if (opt.isPresent()) { ... }
(类似传统判空,未发挥 Optional 优势),优先用 ifPresent
或链式调用。
ifPresent(Consumer<? super T>)
若 Optional
有值,将值传入 consumer
函数执行逻辑;若为空,不做任何操作。
Optional<String> opt = Optional.of("Java");
// 有值时打印,无值时不执行
opt.ifPresent(value -> System.out.println("值为:" + value)); // 输出:值为:Java
// 简化写法(方法引用)
opt.ifPresent(System.out::println); // 输出:Java
为什么用 ? super T
而不是 T
:
允许更广泛的Consumer类型
Optional<String> optionalStr = Optional.of("hello");
// 这些都可以正常工作:
optionalStr.ifPresent((Object obj) -> System.out.println(obj.toString()));
optionalStr.ifPresent((String str) -> System.out.println(str.toUpperCase()));
optionalStr.ifPresent((CharSequence cs) -> System.out.println(cs.length()));
// 如果定义为 Consumer<T>,上面第一个就会编译错误
PECS原则的应用
- Producer(生产者):使用
? extends T
(如:Optional<T>
的get()
) - Consumer(消费者):使用
? super T
(如:ifPresent()
的参数)
ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
(Java 9+)
作用:增强版 ifPresent
:有值时执行 action
,无值时执行 emptyAction
Optional<String> opt1 = Optional.of("Java");
// 有值时执行 action,无值时执行 emptyAction
opt1.ifPresentOrElse(
value -> System.out.println("有值:" + value), // 有值逻辑
() -> System.out.println("无值") // 无值逻辑
); // 输出:有值:Java
Optional<String> opt2 = Optional.empty();
opt2.ifPresentOrElse(
value -> System.out.println("有值:" + value),
() -> System.out.println("无值")
); // 输出:无值
案例
处理数据库查询结果(可能为 null)
// 模拟从数据库查询用户(可能返回 null)
public Optional<User> getUserById(Long id) {
User user = jdbcTemplate.queryForObject("SELECT * FROM user WHERE id = ?", new Object[]{id}, User.class);
return Optional.ofNullable(user); // 包装查询结果
}
// 调用方法:处理空值
Optional<User> userOpt = getUserById(1L);
String userName = userOpt.map(User::getName)
.orElseThrow(() -> new RuntimeException("用户不存在"));
处理集合中的元素(避免遍历中 NPE)
List<User> userList = Arrays.asList(
new User("Alice", 20),
null, // 集合中可能有 null
new User("Bob", 22)
);
// 遍历集合,提取非空用户的姓名(用 Optional 过滤 null)
List<String> nameList = userList.stream()
.map(Optional::ofNullable) // 包装每个元素(处理 null)
.filter(Optional::isPresent) // 过滤空值
.map(Optional::get) // 提取值
.map(User::getName) // 提取姓名
.collect(Collectors.toList());
System.out.println(nameList); // 输出:[Alice, Bob]
处理方法参数(避免参数为 null)
// 方法参数用 Optional 包装,明确参数可能为空
public void createOrder(Optional<String> userId, Optional<Double> amount) {
// 必须有用户 ID,否则抛异常
String requiredUserId = userId.orElseThrow(() -> new RuntimeException("用户 ID 不能为空"));
// 金额默认 0.0
double orderAmount = amount.orElse(0.0);
// 后续创建订单逻辑...
}
// 调用方法
createOrder(Optional.of("1001"), Optional.of(99.9)); // 正常调用
// createOrder(Optional.empty(), Optional.of(99.9)); // 抛异常:用户 ID 不能为空
常见错误用法
- 不要用 Optional 包装集合 / 数组
集合(如List
)本身可以为空(null
)或为空集合(new ArrayList<>()
),用Optional<List>
会增加复杂度,推荐直接返回空集合(而非null
)。
错误:Optional<List<User>> getUsers()
正确:List<User> getUsers()
(无数据时返回Collections.emptyList()
) - 不要用 Optional 作为类的成员变量 / 方法参数(特殊场景除外)
类的成员变量用Optional
会增加序列化 / 反序列化复杂度,且大多数场景下,用null
配合默认值即可;方法参数仅在 “参数可选且需要明确空值逻辑” 时用,否则直接用普通类型(非空参数加@NonNull
注解)。 - 不要过度依赖
isPresent() + get()
这种写法本质和传统if (value != null)
一致,未发挥 Optional 的链式调用优势,优先用ifPresent
、map
、orElse
等方法。 - 不要在
orElse
中写耗时逻辑orElse
的参数会提前执行(无论 Optional 是否有值),耗时逻辑(如数据库查询)应放在orElseGet
中(懒加载)。
功能 | 方法 | 关键说明 |
---|---|---|
创建实例 | of(T) | 包装非空值,null 抛 NPE |
ofNullable(T) | 包装可能为 null 的值,安全 | |
empty() | 创建无值实例 | |
获取值 | get() | 有值返回,无值抛异常,尽量避免 |
orElse(T) | 无值返回默认值,默认值提前创建 | |
orElseGet(Supplier) | 无值动态生成默认值,懒加载 | |
orElseThrow(Supplier) | 无值抛自定义异常,推荐用于异常场景 | |
过滤值 | filter(Predicate) | 满足条件保留值,否则返回 empty |
转换值 | map(Function) | 转换普通值,不处理嵌套 Optional |
flatMap(Function) | 转换 Optional 值,扁平化嵌套 | |
判断值 | isPresent() | 返回 boolean,避免单独使用 |
ifPresent(Consumer) | 有值执行逻辑,无值不操作 | |
ifPresentOrElse(Consumer, Runnable) | 有值 / 无值分别执行逻辑(Java 9+) |
本文系作者 @xiin 原创发布在To Future$站点。未经许可,禁止转载。
暂无评论数据