Optional类
Optional类是一个容器,可以保存任何对象,并且针对NullPointerException空指针异常做了优化,保证Optional类保存的值不会是null。
因此Optional类是针对“对象可能是null也可能不是null”的场景为开发者提供了优质的解决方案,减少了繁琐的异常处理。
通过类型系统,明确地告诉 API 的使用者,一个方法的返回值可能是缺失的,并强制调用者以一种安全的方式处理这种可能性。
1.empty()
返回一个表示空值的Optional实例,即Optional实例的成员变量value=null。
2.filter()
如果value有值并且与给定条件匹配,则返回一个包含该值的Optional实例。否则返回一个表示空值的Optional实例。
所以,filter是为了获取符合条件的对象。
Optional.ofNullable(user).filter(t -> t.getUserName().equalsIgnoreCase("t"))
.ifPresent(t -> log.info("testFilter: {}", t.getUserName()));
// 比较以下的写法
if (user2 != null && user2.getUserName().equalsIgnoreCase("t")) {
user3 = user2;
} else {
user3 = new User();
}
3.get()
如果Optional实例的value是有值的,则返回value值,否则抛出异常NoSuchElementException。
4.of(T value)
要求传入的value不能为空,否则抛出NullPointerException异常。
5.ofNullable(T value)
允许传入的value为空,返回一个Optional.empty()。
6.orElse(T other)
如果Optional实例的value是有值的,则返回value值,否则返回参数值。
7.isPresent
如果value存在(不为空)为true,否则为false。
8.ifPresent(Consumer)
如果Optional实例有值则为其调用consumer,否则不做处理。没有返回值。
所以如果需要返回value值,可以用orElse。
如果需要进一步的逻辑处理,可以用ifPresent。
相对于以下写法,使用IfPresent更简洁,而且可以和filter,map等装配。
if (user != null) {
log.info("testIfPresent: {}", user.getUserName());
}
9.map(Function mapper)
如果Optional实例的value是有值的,执行mapper函数得到返回值。
如果返回值不为空,则对返回值进行封装成Optional实例并返回。
否则,返回Optional.empty()。
String userName = Optional.ofNullable(user).map(User::getUserName).orElse("");
注意:上面语句中判断了user对象是否为空,如果user对象不为空,还进一步判断了getUserName()是否为空。
如果user对象为空或者getUserName()为空,都会通过orElse()判断。
if (ObjectUtils.isEmpty(user2)) {
userName= "";
} else {
userName = user2.getUserName();
}
10.Flatmap(Function mapper)
和map()区别在于:Flatmap要求参数的类型为Optional,或者说要求mapper函数返回值的类型为Optional,并直接返回,不再封装。
Optional<String> optionalS = Optional.ofNullable(user).flatMap(t -> Optional.ofNullable(t.getUserName()));
另外,在stream中,Flatmap用于处理集合的扁平化操作。
11. IfPresentOrElse(Consumer, Runnable)
如果Optional实例有值则为其调用consumer,否则调用Runnable。没有返回值。
Optional.ofNullable(getUsers()).ifPresentOrElse(t -> log.info("users is not null " + t), () -> {
log.info("users is null " + new ArrayList<>(Arrays.asList("A1", "A2")));
});
相对于以下写法,使用IfPresentOrElse更简洁,而且可以和filter,map等装配。
List<String> users = getUsers();
if (users != null && users.isEmpty()) {
log.info("users is not null " + users);
} else {
log.info("users is null " + new ArrayList<>(Arrays.asList("A1", "A2")));
}
使用Optional类的意义
嵌套对象String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();
每一步都可能触发NullPointerException异常。
解决:
String isocode = Optional.ofNullable(user)
.flatmap(user -> user::getAddress)
.flatmap(address -> address::getCountry)
.flatmap(country -> country::getIsocode)
.orElse(“default”).toUpperCase();
使用场合
不应该用在类的字段,或者是方法的参数。这样让代码复杂而且没有必要。
可以作为方法的返回值。
错误的姿势:常见的 Optional 反模式
1: 使用 isPresent() + get()
// 典型反模式
Optional<User> optUser = findUserById(1L);
if (optUser.isPresent()) {
User user = optUser.get();
System.out.println("User name: " + user.getName());
} else {
System.out.println("User not found.");
}
2: 在任何地方都滥用 get()
直接调用 get() 而不进行任何检查,是 Optional 用法中最危险的行为。如果 Optional 为空,调用 get() 会抛出 NoSuchElementException,这无异于将 NullPointerException 换成了另一种运行时异常。
// 危险! Optional<User> optUser = findUserById(2L); // 假设没找到,返回 Optional.empty() String name = optUser.get().getName(); // 抛出 NoSuchElementException
3: 在字段和方法参数中使用 Optional
- 不应作为类字段:
Optional没有实现Serializable接口,在需要序列化的场景(如 JPA/Hibernate 实体、DTO)下会引发问题。此外,它为字段增加了一层不必要的包装,增加了复杂性。一个类要么有这个属性,要么没有,用null表示字段的缺失是可接受的。 - 不应作为方法参数: 这会强迫方法的调用者去创建一个
Optional对象来包装参数,非常不便。如果一个方法需要处理可选参数,更好的方式是方法重载
// 不推荐
public void process(Optional<String> config) { ... }
// 推荐
public void process() { ... } // 无配置
public void process(String config) { ... } // 有配置
正确的姿势:拥抱函数式 API
A. 安全地获取值或提供替代方案
orElse(T other): 如果 Optional 不为空,返回值;否则,返回 other 这个默认值。
// 如果找不到用户,返回一个 "Guest" 用户
User user = findUserById(3L).orElse(new User("Guest"));
orElseGet(Supplier<? extends T> supplier): 与 orElse 类似,但它接受一个 Supplier 函数。只有当 Optional 为空时,这个函数才会被执行。如果创建默认值的成本很高,这是最佳选择(懒加载)。
// 只有在找不到用户时,才会执行昂贵的 new DefaultUser() 操作 User user = findUserById(4L).orElseGet(() -> createDefaultUser());
orElseThrow(Supplier<? extends X> exceptionSupplier): 如果 Optional 为空,则抛出由 supplier 创建的异常。这是处理“值必须存在,否则就是错误”场景的标准方式。
// 如果找不到用户,就抛出自定义异常
User user = findUserById(5L).orElseThrow(() -> new UserNotFoundException("User not found"));
B. 当值存在时执行操作
替代 if (opt.isPresent()) { ... }。
ifPresent(Consumer<? super T> consumer): 如果值存在,就对其执行 consumer 操作。
findUserById(6L).ifPresent(user -> System.out.println("Found user: " + user.getName()));
ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) (Java 9+): 如果值存在,执行 action;否则,执行 emptyAction。
findUserById(7L).ifPresentOrElse(
user -> System.out.println("Logged in as: " + user.getName()),
() -> System.out.println("Please log in.")
);
C. 转换值 (核心函数式特性)
map, flatMap, filter 是 Optional 的精髓,它们允许你在不破坏 Optional 封装的情况下,对内部的值进行链式操作。
map(Function<? super T, ? extends U> mapper): 如果值存在,将其通过 mapper 函数转换为另一个值,并用一个新的 Optional 包装起来。如果 Optional 为空,则返回一个空的 Optional。
// 获取用户名的 Optional Optional<String> userNameOpt = findUserById(8L).map(User::getName);
flatMap(Function<? super T, Optional<U>> mapper): 与 map 类似,但要求 mapper 函数的返回值本身就是一个 Optional。
这在处理返回 Optional 的多层嵌套调用时非常有用,可以避免出现 Optional<Optional<T>> 这样的窘境。
// 假设 User 有一个返回 Optional<Profile> 的方法
// flatMap 可以将 Optional<User> -> Optional<Profile> -> Optional<String> 的链条压平
Optional<String> profileImageUrl = findUserById(9L)
.flatMap(User::getProfile)
.map(Profile::getImageUrl);
filter(Predicate<? super T> predicate): 如果值存在且满足 predicate 条件,则返回包含该值的 Optional;否则返回空的 Optional。
// 只处理激活状态的用户
findUserById(10L)
.filter(User::isActive)
.ifPresent(activeUser -> System.out.println(activeUser.getName() + " is active."));
D. 链式调用:将它们组合起来
将以上方法组合起来,可以写出极其简洁、流畅且安全的代码。
// 示例:查找ID为11的用户,如果他是管理员,则获取其大写的邮箱地址,否则返回默认的管理员邮箱
String adminEmail = findUserById(11L)
.filter(User::isAdmin) // 筛选出管理员
.map(User::getEmail) // 获取他的邮箱
.map(String::toUpperCase) // 转换为大写
.orElse("default.admin@example.com"); // 如果以上任何一步为空,则返回默认值
这段代码一气呵成,没有任何 if-null 判断,却完美地处理了所有可能为空的情况。
总结与最佳实践清单
- • ✅ 应当 将
Optional作为方法的返回值,明确告知调用者该值可能缺失。 - • ✅ 应当 使用
orElse,orElseGet,orElseThrow来处理值缺失的情况。 - • ✅ 应当 使用
map,flatMap,filter来进行安全的链式操作。 - • ✅ 应当 使用
ifPresent或ifPresentOrElse来替代if (isPresent())。 - • ❌ 不应 将
Optional用作类的字段(属性)。 - • ❌ 不应 将
Optional用作方法的参数。 - • ❌ 不应 为了调用
get()而使用isPresent()进行判断。 - • ❌ 不应 在没有检查的情况下直接调用
get()。 - • ❌ 不应 用
Optional包装集合类型(如Optional<List<User>>),应直接返回空集合。