深入解析 Java Stream API:筛选根节点的优雅实现!!!
🚀 深入解析 Java Stream API:筛选根节点的优雅实现 🔧
大家好!👋 今天我们来聊聊 Java 8 中一个非常常见的操作:使用 Stream API 从 List 中筛选出特定条件的元素。🎉 具体来说,我们将深入分析以下代码片段:
List<InviteCode> roots = inviteCodes.stream()
.filter(ic -> ic.getCreatedBy() == null)
.collect(Collectors.toList());
这段代码看似简单,但背后涉及了 Stream API、Lambda 表达式以及 Collectors.toList 的强大功能。💡 我们将从代码的背景开始,逐步拆解它的实现原理,探讨使用场景、优势和优化方法,最后通过一个实际案例展示它的应用。为了更直观地理解整个过程,我们还会插入一个 Mermaid 流程图!📊
准备好了吗?让我们开始吧!🚀
📖 背景:为什么需要筛选根节点?
在 Java 开发中,我们经常需要处理层级数据。例如,在一个邀请码系统中,我们有一个 List<InviteCode>,其中 InviteCode 是一个实体类,包含以下字段:
public class InviteCode {
private Integer id;
private String inviteCode;
private Integer inviteLevel;
private Integer createdBy;
// Getters and Setters
public Integer getId() {
return id;
}
public String getInviteCode() {
return inviteCode;
}
public Integer getInviteLevel() {
return inviteLevel;
}
public Integer getCreatedBy() {
return createdBy;
}
}
假设我们有一个 List<InviteCode>,包含 adminId = 7 的所有邀请码记录:
| id | admin_id | created_by | invite_code | invite_level |
|---|---|---|---|---|
| 20 | 7 | NULL | ****** | 0 |
| 21 | 7 | 20 | 263113 | 1 |
| 22 | 7 | 20 | 704358 | 1 |
| 23 | 7 | 20 | 982868 | 1 |
| 24 | 7 | NULL | ****** | 0 |
| 25 | 7 | 24 | ****** | 1 |
| 26 | 7 | 25 | ****** | 2 |
| 27 | 7 | 26 | 991476 | 3 |
我们的目标是构建一个以 adminId 为根的邀请码层级树。层级树的构建需要从根节点开始,而根节点的定义是 createdBy == null 的 InviteCode 对象。换句话说:
createdBy == null:表示这是一个根节点(没有父节点)。createdBy != null:表示这是一个子节点(有父节点,createdBy是父节点的id)。
因此,我们需要从 List<InviteCode> 中筛选出所有 createdBy == null 的 InviteCode 对象,这就是以下代码的作用:
List<InviteCode> roots = inviteCodes.stream()
.filter(ic -> ic.getCreatedBy() == null)
.collect(Collectors.toList());
🌟 代码拆解:一步步理解
让我们逐步拆解这段代码,弄清楚它是如何工作的!
1. inviteCodes.stream()
inviteCodes:是一个List<InviteCode>,包含adminId = 7的 8 条记录(id = 20, 21, ..., 27)。stream():将List<InviteCode>转换为一个Stream<InviteCode>。Stream是 Java 8 引入的流式 API,允许你以声明式的方式处理集合数据(例如映射、过滤、归约等)。
结果:inviteCodes.stream() 生成了一个 Stream<InviteCode>,包含 8 个 InviteCode 对象。
2. .filter(ic -> ic.getCreatedBy() == null)
filter:是 Stream API 的一个中间操作,用于筛选流中的元素。ic -> ic.getCreatedBy() == null:这是一个 Lambda 表达式,表示一个谓词(Predicate),用于判断每个InviteCode对象是否满足条件。ic:代表流中的每个InviteCode对象。ic.getCreatedBy():获取InviteCode对象的createdBy字段(Integer类型)。ic.getCreatedBy() == null:检查createdBy是否为null。
- 作用:
filter会保留所有满足条件的元素(createdBy == null的InviteCode),丢弃不满足条件的元素。
类型:Predicate<InviteCode>,将 InviteCode 映射为一个布尔值(true 或 false)。
3. .collect(Collectors.toList())
collect:是 Stream API 的终止操作,用于将流中的元素收集到一个结果容器中(例如List、Set或Map)。Collectors.toList():是一个收集器(Collector),专门用于将流中的元素收集到一个List中。
结果:collect(Collectors.toList()) 将筛选后的 Stream<InviteCode> 收集到一个新的 List<InviteCode> 中。
4. 整体效果
inviteCodes.stream().filter(ic -> ic.getCreatedBy() == null).collect(Collectors.toList()):- 从
inviteCodes创建一个Stream<InviteCode>。 - 筛选出所有
createdBy == null的InviteCode对象。 - 将筛选结果收集到一个新的
List<InviteCode>中。
- 从
- 赋值:将结果赋值给
roots,roots是一个List<InviteCode>,包含所有根节点。
📊 Mermaid 流程图:可视化筛选过程
为了更直观地理解从 List<InviteCode> 筛选根节点的过程,我们使用 Mermaid 流程图来展示:
- 流程说明:
- 从
List<InviteCode>开始,转换为Stream<InviteCode>。 - 对流中的每个
InviteCode对象:- 检查
createdBy == null。 - 如果
true,保留该对象;如果false,丢弃。
- 检查
- 将筛选后的元素收集到
List<InviteCode>中。
- 从
📝 示例:具体数据
假设 inviteCodes 包含以下数据(adminId = 7):
| id | admin_id | created_by | invite_code | invite_level |
|---|---|---|---|---|
| 20 | 7 | NULL | ****** | 0 |
| 21 | 7 | 20 | 263113 | 1 |
| 22 | 7 | 20 | 704358 | 1 |
| 23 | 7 | 20 | 982868 | 1 |
| 24 | 7 | NULL | ****** | 0 |
| 25 | 7 | 24 | ****** | 1 |
| 26 | 7 | 25 | ****** | 2 |
| 27 | 7 | 26 | 991476 | 3 |
1. inviteCodes.stream()
- 生成了一个
Stream<InviteCode>,包含 8 个InviteCode对象(id = 20, 21, ..., 27)。
2. .filter(ic -> ic.getCreatedBy() == null)
- 对每个
InviteCode对象检查createdBy是否为null:id = 20:createdBy = null,保留。id = 21:createdBy = 20,丢弃。id = 22:createdBy = 20,丢弃。id = 23:createdBy = 20,丢弃。id = 24:createdBy = null,保留。id = 25:createdBy = 24,丢弃。id = 26:createdBy = 25,丢弃。id = 27:createdBy = 26,丢弃。
结果:筛选后的 Stream<InviteCode> 只包含 2 个元素:
InviteCode(id=20, createdBy=null, ...)。InviteCode(id=24, createdBy=null, ...)。
3. .collect(Collectors.toList())
- 将筛选后的
Stream<InviteCode>收集到一个新的List<InviteCode>中。
结果:roots 是一个 List<InviteCode>,包含以下 2 个元素:
InviteCode(id=20, createdBy=null, ...)。InviteCode(id=24, createdBy=null, ...)。
🌟 为什么需要 roots?
在邀请码系统中,我们的目标是构建一个以 adminId 为根的层级树。层级树的构建需要从根节点开始,递归地查找子节点:
// 找到所有根节点(createdBy = NULL)
List<InviteCode> roots = inviteCodes.stream()
.filter(ic -> ic.getCreatedBy() == null)
.collect(Collectors.toList());
// 为每个根节点构建树形结构
List<InviteCodeTreeDTO> trees = new ArrayList<>();
for (InviteCode root : roots) {
InviteCodeTreeDTO tree = buildTree(root, inviteCodeMap, new HashSet<>());
trees.add(tree);
}
- 层级树:
id = 20是第一个根节点(createdBy = null),它的子节点是id = 21, 22, 23(createdBy = 20)。id = 24是第二个根节点(createdBy = null),它的子节点是id = 25(createdBy = 24),以此类推。
- 筛选根节点的作用:
- 层级树的构建需要从根节点开始,
createdBy == null表示这是一个根节点(没有父节点)。
- 层级树的构建需要从根节点开始,
🚀 优势:为什么使用 Stream API?
1. 代码简洁
- Stream API 提供了声明式的写法,比传统的
for循环更简洁。 - 传统写法可能需要手动遍历和填充
List:List<InviteCode> roots = new ArrayList<>(); for (InviteCode ic : inviteCodes) { if (ic.getCreatedBy() == null) { roots.add(ic); } } - 使用 Stream API,代码更简洁优雅。
2. 功能强大
- Stream API 支持链式操作,可以轻松添加其他过滤条件。
- 例如,如果只想筛选
inviteLevel == 0的根节点:List<InviteCode> roots = inviteCodes.stream() .filter(ic -> ic.getCreatedBy() == null && ic.getInviteLevel() == 0) .collect(Collectors.toList());
3. 并行处理
- Stream API 支持并行处理(
parallelStream()),在大规模数据下可以提高性能:List<InviteCode> roots = inviteCodes.parallelStream() .filter(ic -> ic.getCreatedBy() == null) .collect(Collectors.toList());
🛠️ 优化建议
1. 添加日志
可以在筛选根节点后添加日志,方便调试:
List<InviteCode> roots = inviteCodes.stream()
.filter(ic -> ic.getCreatedBy() == null)
.collect(Collectors.toList());
logger.info("Found {} root nodes for adminId {}: {}", roots.size(), adminId, roots);
- 效果:记录找到的根节点数量和详细信息。
2. 并行处理
如果 inviteCodes 非常大,可以使用 parallelStream() 提高性能:
List<InviteCode> roots = inviteCodes.parallelStream()
.filter(ic -> ic.getCreatedBy() == null)
.collect(Collectors.toList());
- 注意:并行流适合大数据量,但在小数据量下可能反而更慢(因为线程开销)。
3. 空列表处理
如果 roots 为空,可以提前返回:
List<InviteCode> roots = inviteCodes.stream()
.filter(ic -> ic.getCreatedBy() == null)
.collect(Collectors.toList());
if (roots.isEmpty()) {
AdminInviteCodeTreeDTO result = new AdminInviteCodeTreeDTO();
result.setAdminId(adminId);
result.setChildren(Collections.emptyList());
return result;
}
- 效果:避免不必要的树构建操作。
📝 完整代码:实际应用
以下是完整的 InviteCodeService 实现,展示了如何使用 roots 构建层级树:
public class InviteCodeService {
private final InviteCodeRepository inviteCodeRepository;
private static final Logger logger = LoggerFactory.getLogger(InviteCodeService.class);
public InviteCodeService(InviteCodeRepository inviteCodeRepository) {
this.inviteCodeRepository = inviteCodeRepository;
}
public AdminInviteCodeTreeDTO getAdminInviteCodeTree(Integer adminId) {
List<InviteCode> inviteCodes = inviteCodeRepository.findByAdminId(adminId);
if (inviteCodes.isEmpty()) {
AdminInviteCodeTreeDTO result = new AdminInviteCodeTreeDTO();
result.setAdminId(adminId);
result.setChildren(Collections.emptyList());
return result;
}
// 将 List<InviteCode> 转换为 Map<Integer, InviteCode>
Map<Integer, InviteCode> inviteCodeMap = inviteCodes.stream()
.collect(Collectors.toMap(InviteCode::getId, ic -> ic));
// 预构建 createdBy 到子节点的映射
Map<Integer, List<InviteCode>> childrenMap = inviteCodes.stream()
.filter(ic -> ic.getCreatedBy() != null)
.collect(Collectors.groupingBy(InviteCode::getCreatedBy));
// 找到所有根节点(createdBy = NULL)
List<InviteCode> roots = inviteCodes.stream()
.filter(ic -> ic.getCreatedBy() == null)
.collect(Collectors.toList());
logger.info("Found {} root nodes for adminId {}: {}", roots.size(), adminId, roots);
// 如果没有根节点,直接返回
if (roots.isEmpty()) {
AdminInviteCodeTreeDTO result = new AdminInviteCodeTreeDTO();
result.setAdminId(adminId);
result.setChildren(Collections.emptyList());
return result;
}
// 为每个根节点构建树形结构
List<InviteCodeTreeDTO> trees = new ArrayList<>();
for (InviteCode root : roots) {
InviteCodeTreeDTO tree = buildTree(root, inviteCodeMap, childrenMap, new HashSet<>());
trees.add(tree);
}
AdminInviteCodeTreeDTO result = new AdminInviteCodeTreeDTO();
result.setAdminId(adminId);
result.setChildren(trees);
return result;
}
private InviteCodeTreeDTO buildTree(InviteCode root, Map<Integer, InviteCode> inviteCodeMap, Map<Integer, List<InviteCode>> childrenMap, Set<Integer> visited) {
if (!visited.add(root.getId())) {
throw new IllegalStateException("Detected a cycle in invite code hierarchy at ID: " + root.getId());
}
InviteCodeTreeDTO node = new InviteCodeTreeDTO();
node.setId(root.getId());
node.setInviteCode(root.getInviteCode());
node.setInviteLevel(root.getInviteLevel());
node.setChildren(new ArrayList<>());
List<InviteCode> children = childrenMap.getOrDefault(root.getId(), Collections.emptyList());
for (InviteCode child : children) {
InviteCodeTreeDTO childNode = buildTree(child, inviteCodeMap, childrenMap, new HashSet<>(visited));
node.getChildren().add(childNode);
}
return node;
}
}
🎉 总结
通过 Stream API 和 Collectors.toList,我们可以轻松地从 List<InviteCode> 中筛选出根节点,为后续的层级树构建提供了基础。💻
- 核心代码:
inviteCodes.stream().filter(ic -> ic.getCreatedBy() == null).collect(Collectors.toList())筛选出根节点。 - 优势:代码简洁、功能强大、支持并行处理。
- 优化:添加日志、提前处理空列表、支持并行流。
希望这篇博客对你理解 Stream API 和 filter 操作有所帮助!💬 如果你有其他问题,欢迎留言讨论!🚀
📚 参考:Java 官方文档、Collectors 源码。点赞和分享哦!😊

