上篇学了 Optional 基础用法。今天深入 filter、ifPresent、以及如何在博客项目中用 Optional 重构查询逻辑。
一、filter:条件过滤
filter 接收一个 Predicate,如果 Optional 有值且满足条件,返回包含该值的 Optional;否则返回空。
Optional<BlogPost> result = repository.findById(2L)
.filter(BlogPost::isPublished);
// id=2 存在且已发布 -> Optional[BlogPost]
// id=2 存在但未发布 -> Optional.empty
// id=2 不存在 -> Optional.empty
链式 filter:多个条件依次过滤。
Optional<BlogPost> hot = repository.findById(2L)
.filter(BlogPost::isPublished)
.filter(b -> b.getViews() > 200);
二、ifPresent:有值时执行
repository.findById(1L).ifPresent(blog ->
System.out.println("找到: " + blog.getTitle()));
// 找到就打印,没找到什么都不做
链式处理:先转换再消费。
repository.findById(2L)
.map(BlogPost::getTitle)
.ifPresent(title -> System.out.println("标题: " + title));
三、Optional 与 Stream 结合
在 Stream 中处理 Optional 时,需要过滤空值:
// Java 8 兼容写法
List<String> titles = Arrays.asList(1L, 2L, 3L).stream()
.map(id -> repository.findById(id))
.filter(Optional::isPresent)
.map(Optional::get)
.map(BlogPost::getTitle)
.collect(Collectors.toList());
注意:
Optional.stream()是 Java 9+ 才有的方法,Java 8 中需要用filter + get替代。
四、Repository 层用 Optional 重构
Spring Data JPA 的 JpaRepository.findById() 本身就返回 Optional<T>,这是推荐的做法。
传统写法
BlogPost blog = repository.findById(id);
if (blog == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "博客不存在");
}
return Result.success(blog);
Optional 写法
BlogPost blog = repository.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "博客不存在"));
return Result.success(blog);
一行代码替代 if-null 抛异常。
存在就执行操作
// 存在就更新
repository.findById(id).ifPresent(existing -> {
existing.setTitle(newTitle);
repository.save(existing);
});
// 存在就删除
repository.findById(id).ifPresent(repository::delete);
// 不存在就创建
repository.findById(id).orElseGet(() -> repository.save(new BlogPost()));
五、orElse vs orElseGet 的陷阱
Optional<BlogPost> found = repository.findById(1L);
// orElse:即使找到了,new BlogPost() 也会执行
BlogPost blog = found.orElse(new BlogPost());
// orElseGet:只在没找到时才执行
BlogPost blog = found.orElseGet(() -> new BlogPost());
当默认值的创建有开销(如数据库查询、对象创建)时,优先用 orElseGet。
六、运行结果
=== Optional filter ===
id=2 且已发布: Stream 详解
id=3 且已发布: 未找到
id=2 已发布且>200阅读: Stream 详解
=== Optional ifPresent ===
找到: Lambda 入门 (200 阅读)
标题: Stream 详解
=== Optional + Stream ===
查 id 1,2,3 的标题: [Lambda 入门, Stream 详解, Vue3 API]
Java 8 兼容写法: [Lambda 入门, Stream 详解, Vue3 API]
=== Repository 层 Optional 实践 ===
标题: Lambda 入门
id=99: 博客不存在
更新成功: 更新后的标题
七、Optional 最佳实践
| 场景 | 推荐做法 |
|---|---|
| 返回值可能为空 | 用 Optional<T> 包装 |
| 链式属性获取 | flatMap + map + orElse |
| 为空时提供默认值 | orElse / orElseGet |
| 为空时抛异常 | orElseThrow |
| 为空时不执行 | ifPresent |
| 条件过滤 | filter |
| 不要做的事 | |
用 get() 直接取值 | 用 orElse / ifPresent 替代 |
| 用 Optional 做字段类型 | Optional 设计为返回值,不是字段类型 |
| 用 Optional 做方法参数 | 用重载方法替代 |
八、下篇预告
周六我们将在博客项目中用 Optional 重构现有的 Controller 查询逻辑,告别所有的 if-null 判断。
系列文章导航:
更多请关注我的微信公众号,感谢!