前两周学了 Lambda、函数式接口和 Stream 基础。今天在一个完整的博客接口场景中,用 Stream 实现排序、过滤、分页、统计功能。
一、通用分页:排序 + skip + limit
分页是后端最常见的功能。用 Stream 实现只需要三行:
public List<Blog> getBlogsByPage(int page, int pageSize) {
return blogs.stream()
.sorted(Comparator.comparing(Blog::getCreateTime).reversed()) // 最新在前
.skip((long) (page - 1) * pageSize) // 跳过前面的数据
.limit(pageSize) // 取一页
.collect(Collectors.toList());
}
核心公式:skip((page - 1) * pageSize)
| 页码 | pageSize | skip | limit |
|---|---|---|---|
| 第1页 | 10 | 0 | 10 |
| 第2页 | 10 | 10 | 10 |
| 第3页 | 10 | 20 | 10 |
二、带条件的分页:先过滤再分页
实际场景中,分页往往带有筛选条件:
// 只看已发布的文章,按阅读量降序分页
public List<Blog> getPublishedByPage(int page, int pageSize) {
return blogs.stream()
.filter(Blog::isPublished)
.sorted(Comparator.comparingInt(Blog::getViews).reversed())
.skip((long) (page - 1) * pageSize)
.limit(pageSize)
.collect(Collectors.toList());
}
三、搜索 + 分页
关键词搜索后分页返回:
public List<Blog> searchAndPaginate(String keyword, int page, int pageSize) {
return blogs.stream()
.filter(b -> b.getTitle().toLowerCase().contains(keyword.toLowerCase())
|| b.getAuthor().contains(keyword))
.skip((long) (page - 1) * pageSize)
.limit(pageSize)
.collect(Collectors.toList());
}
// 同时获取总数
long total = blogs.stream()
.filter(b -> b.getTitle().toLowerCase().contains(keyword.toLowerCase()))
.count();
四、统计面板:分组 + 聚合
按作者统计文章数和总阅读量
Map<String, AuthorStats> stats = blogs.stream()
.collect(Collectors.groupingBy(
Blog::getAuthor,
Collectors.collectingAndThen(
Collectors.toList(),
list -> new AuthorStats(
list.size(),
list.stream().mapToInt(Blog::getViews).sum()
)
)
));
输出:
张三: 3 篇, 总阅读 530
李四: 2 篇, 总阅读 750
王五: 2 篇, 总阅读 750
按标签统计文章数
blogs.stream()
.flatMap(b -> b.getTags().stream()) // 展平所有标签
.collect(Collectors.groupingBy(tag -> tag, Collectors.counting()))
.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.forEach(entry -> System.out.println(" " + entry.getKey() + ": " + entry.getValue() + " 篇"));
输出:
Java: 4 篇
前端: 2 篇
Optional: 1 篇
Docker: 1 篇
...
五、数据校验:allMatch / anyMatch
用 Stream 做数据校验也很方便:
// 所有文章都有标题吗?
boolean allHaveTitle = blogs.stream()
.allMatch(b -> b.getTitle() != null && !b.getTitle().isEmpty());
// 有没有作者名为空的?
boolean hasEmptyAuthor = blogs.stream()
.anyMatch(b -> b.getAuthor() == null || b.getAuthor().isEmpty());
六、运行结果
本项目 StreamPaginationDemo.java 完整运行结果:
=== 1. 分页查询 ===
第 1 页 (每页3条):
[2026-06-12T12:00] Java 8 Optional 最佳实践
[2026-06-11T08:00] Docker 入门指南
[2026-06-09T11:00] React Hooks 教程
第 2 页:
[2026-06-07T16:00] Java Stream API 详解
[2026-06-05T09:00] Vue3 组合式 API
[2026-06-03T14:00] Spring Boot 实战
=== 2. 带条件分页 ===
已发布-第1页-按阅读量降序:
[500阅读] Vue3 组合式 API
[400阅读] React Hooks 教程
=== 3. 搜索 + 分页 ===
搜索 'Java' 第1页:
Java 8 Lambda 入门 (张三)
Java Stream API 详解 (张三)
Java 8 Optional 最佳实践 (张三)
共 3 条结果
=== 4. 统计面板 ===
张三: 3 篇, 总阅读 530
李四: 2 篇, 总阅读 750
王五: 2 篇, 总阅读 750
各标签文章数:
Java: 4 篇
前端: 2 篇
=== 5. 数据校验 ===
所有文章都有标题: true
有空作者名: false
七、本周学习总结
第三周我们学习了 Stream API,完整路线:
周二:Stream API 入门——创建、中间操作、终端操作
周四:filter/map/sorted——三个最常用的操作
周六:分页+搜索+统计——博客接口实战(本篇)
核心收获:
| 知识点 | 关键方法 | 一句话 |
|---|---|---|
| 创建 Stream | stream() / of() / generate() | 数据源 → 流 |
| 过滤 | filter(Predicate) | 只要匹配的 |
| 转换 | map(Function) | 一对一转换 |
| 扁平化 | flatMap | 一对多再展平 |
| 排序 | sorted(Comparator) | 按规则排序 |
| 分页 | skip(n) + limit(n) | 跳过 + 取 |
| 统计 | count() / sum() / average() | 聚合计算 |
| 匹配 | anyMatch / allMatch / noneMatch | 是否有匹配 |
八、下周预告
第四周进入 Stream 进阶:Collectors 全家桶(toList/joining/groupingBy/partitioningBy)、flatMap 深入、以及数据统计的完整实战。
系列文章导航:
更多请关注我的微信公众号,感谢!