🚀 深入解析 Java Stream API:筛选子节点的优雅实现 🔧
大家好!👋 今天我们来聊聊 Java 8 中一个非常常见的操作:使用 Stream API 从 Map 中筛选出特定条件的元素。🎉 具体来说,我们将深入分析以下代码片段:
List<InviteCode> children = inviteCodeMap.values().stream().filter(ic -> Objects.equals(ic.getCreatedBy(), root.getId())).collect(Collectors.toList());
这段代码是构建邀请码层级树的一部分,背后涉及了 Stream API、Lambda 表达式以及 Collectors.toList 的强大功能。💡 我们将从代码的背景开始,逐步拆解它的实现原理,探讨使用场景、优势和优化方法,最后通过一个实际案例展示它的应用。为了更直观地理解整个过程,我们还会插入一个 Mermaid 流程图!📊
准备好了吗?让我们开始吧!🚀
📖 背景:为什么需要筛选子节点?
在 Java 开发中,我们经常需要处理层级数据。例如,在一个邀请码系统中,我们有一个 Map<Integer, InviteCode>,其中 InviteCode 是一个实体类,包含以下字段:
public class InviteCode {private Integer id;private String inviteCode;private Integer inviteLevel;private Integer createdBy;// Getters and Setterspublic Integer getId() {return id;}public String getInviteCode() {return inviteCode;}public Integer getInviteLevel() {return inviteLevel;}public Integer getCreatedBy() {return createdBy;}
}
假设我们有一个 Map<Integer, InviteCode>(inviteCodeMap),包含 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 等于当前节点 id 的 InviteCode 对象。换句话说:
createdBy == root.getId():表示这是一个子节点(root是它的父节点)。createdBy != root.getId():表示这不是root的子节点。
因此,我们需要从 inviteCodeMap 中筛选出所有 createdBy == root.getId() 的 InviteCode 对象,这就是以下代码的作用:
List<InviteCode> children = inviteCodeMap.values().stream().filter(ic -> Objects.equals(ic.getCreatedBy(), root.getId())).collect(Collectors.toList());
🌟 代码拆解:一步步理解
让我们逐步拆解这段代码,弄清楚它是如何工作的!
1. inviteCodeMap.values()
inviteCodeMap:是一个Map<Integer, InviteCode>,键是InviteCode的id,值是InviteCode对象本身。values():Map的方法,返回Map中所有值的Collection(类型为Collection<InviteCode>)。- 在这里,
inviteCodeMap.values()返回一个包含所有InviteCode对象的集合(例如id = 20, 21, ..., 27的 8 个对象)。
- 在这里,
结果:inviteCodeMap.values() 是一个 Collection<InviteCode>,包含所有 InviteCode 对象。
2. .stream()
stream():将Collection<InviteCode>转换为一个Stream<InviteCode>。Stream是 Java 8 引入的流式 API,允许你以声明式的方式处理集合数据(例如映射、过滤、归约等)。
结果:inviteCodeMap.values().stream() 生成了一个 Stream<InviteCode>,包含 inviteCodeMap 中的所有 InviteCode 对象。
3. .filter(ic -> Objects.equals(ic.getCreatedBy(), root.getId()))
filter:是 Stream API 的一个中间操作,用于筛选流中的元素。ic -> Objects.equals(ic.getCreatedBy(), root.getId()):这是一个 Lambda 表达式,表示一个谓词(Predicate),用于判断每个InviteCode对象是否满足条件。ic:代表流中的每个InviteCode对象。ic.getCreatedBy():获取InviteCode对象的createdBy字段(Integer类型)。root.getId():获取当前根节点的id(Integer类型)。Objects.equals(ic.getCreatedBy(), root.getId()):比较ic.getCreatedBy()和root.getId()是否相等。- 使用
Objects.equals而不是直接==是为了安全地处理null值(如果ic.getCreatedBy()为null,==可能会导致问题)。
- 使用
- 作用:
filter会保留所有满足条件的元素(createdBy等于root.getId()的InviteCode),丢弃不满足条件的元素。
类型:Predicate<InviteCode>,将 InviteCode 映射为一个布尔值(true 或 false)。
4. .collect(Collectors.toList())
collect:是 Stream API 的终止操作,用于将流中的元素收集到一个结果容器中(例如List、Set或Map)。Collectors.toList():是一个收集器(Collector),专门用于将流中的元素收集到一个List中。
结果:collect(Collectors.toList()) 将筛选后的 Stream<InviteCode> 收集到一个新的 List<InviteCode> 中。
5. 整体效果
inviteCodeMap.values().stream().filter(ic -> Objects.equals(ic.getCreatedBy(), root.getId())).collect(Collectors.toList()):- 从
inviteCodeMap获取所有InviteCode对象,转换为Stream<InviteCode>。 - 筛选出
createdBy等于root.getId()的InviteCode对象(即root的子节点)。 - 将筛选结果收集到一个新的
List<InviteCode>中。
- 从
- 赋值:将结果赋值给
children,children是一个List<InviteCode>,包含root的所有直接子节点。
📊 Mermaid 流程图:可视化筛选过程
为了更直观地理解从 Map<Integer, InviteCode> 筛选子节点的过程,我们使用 Mermaid 流程图来展示:
- 流程说明:
- 从
Map<Integer, InviteCode>开始,获取所有值(inviteCodeMap.values())。 - 转换为
Stream<InviteCode>。 - 对流中的每个
InviteCode对象:- 检查
createdBy == root.getId()。 - 如果
true,保留该对象;如果false,丢弃。
- 检查
- 将筛选后的元素收集到
List<InviteCode>中。
- 从
📝 示例:具体数据
假设 inviteCodeMap 包含以下数据(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. inviteCodeMap.values().stream()
inviteCodeMap.values()返回一个Collection<InviteCode>,包含 8 个InviteCode对象(id = 20, 21, ..., 27)。.stream()将其转换为Stream<InviteCode>。
2. .filter(ic -> Objects.equals(ic.getCreatedBy(), root.getId()))
假设当前 root 是 id = 20 的 InviteCode:
root.getId() = 20。- 对每个
InviteCode对象检查createdBy是否等于20:id = 20:createdBy = null,Objects.equals(null, 20) = false,丢弃。id = 21:createdBy = 20,Objects.equals(20, 20) = true,保留。id = 22:createdBy = 20,Objects.equals(20, 20) = true,保留。id = 23:createdBy = 20,Objects.equals(20, 20) = true,保留。id = 24:createdBy = null,Objects.equals(null, 20) = false,丢弃。id = 25:createdBy = 24,Objects.equals(24, 20) = false,丢弃。id = 26:createdBy = 25,Objects.equals(25, 20) = false,丢弃。id = 27:createdBy = 26,Objects.equals(26, 20) = false,丢弃。
结果:筛选后的 Stream<InviteCode> 包含 3 个元素:
InviteCode(id=21, createdBy=20, ...)。InviteCode(id=22, createdBy=20, ...)。InviteCode(id=23, createdBy=20, ...)。
3. .collect(Collectors.toList())
- 将筛选后的
Stream<InviteCode>收集到一个新的List<InviteCode>中。
结果:children 是一个 List<InviteCode>,包含以下 3 个元素:
InviteCode(id=21, createdBy=20, ...)。InviteCode(id=22, createdBy=20, ...)。InviteCode(id=23, createdBy=20, ...)。
4. 另一个例子:root = id = 24
root.getId() = 24。- 筛选
createdBy = 24: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,丢弃。
结果:children 包含 1 个元素:
InviteCode(id=25, createdBy=24, ...)。
🌟 为什么需要 children?
在 buildTree 方法中,children 的作用是找到当前节点(root)的所有直接子节点,以便递归构建树形结构:
private InviteCodeTreeDTO buildTree(InviteCode root, Map<Integer, InviteCode> inviteCodeMap) {InviteCodeTreeDTO node = new InviteCodeTreeDTO();node.setId(root.getId());node.setInviteCode(root.getInviteCode());node.setInviteLevel(root.getInviteLevel());node.setChildren(new ArrayList<>());// 查找所有子节点(createdBy = root.id)List<InviteCode> children = inviteCodeMap.values().stream().filter(ic -> Objects.equals(ic.getCreatedBy(), root.getId())).collect(Collectors.toList());// 递归构建子树for (InviteCode child : children) {InviteCodeTreeDTO childNode = buildTree(child, inviteCodeMap);node.getChildren().add(childNode);}return node;
}
- 层级树构建:
- 当前节点
root(例如id = 20)。 - 找到所有子节点(
createdBy = 20的InviteCode,即id = 21, 22, 23)。 - 递归调用
buildTree为每个子节点构建子树。
- 当前节点
- 递归:通过不断查找子节点,构建完整的树形结构。
🚀 优势:为什么使用 Stream API?
1. 代码简洁
- Stream API 提供了声明式的写法,比传统的
for循环更简洁。 - 传统写法可能需要手动遍历和填充
List:List<InviteCode> children = new ArrayList<>(); for (InviteCode ic : inviteCodeMap.values()) {if (Objects.equals(ic.getCreatedBy(), root.getId())) {children.add(ic);} } - 使用 Stream API,代码更简洁优雅。
2. 功能强大
- Stream API 支持链式操作,可以轻松添加其他过滤条件。
- 例如,如果只想筛选
inviteLevel == 1的子节点:List<InviteCode> children = inviteCodeMap.values().stream().filter(ic -> Objects.equals(ic.getCreatedBy(), root.getId()) && ic.getInviteLevel() == 1).collect(Collectors.toList());
3. 并行处理
- Stream API 支持并行处理(
parallelStream()),在大规模数据下可以提高性能:List<InviteCode> children = inviteCodeMap.values().parallelStream().filter(ic -> Objects.equals(ic.getCreatedBy(), root.getId())).collect(Collectors.toList());
🛠️ 优化建议
1. 更高效的子节点查找
当前实现中,inviteCodeMap.values().stream() 需要遍历所有 InviteCode 对象,效率不高。可以通过预先构建一个 Map<Integer, List<InviteCode>> 来存储 createdBy 到子节点的映射:
// 在 getAdminInviteCodeTree 中预构建
Map<Integer, List<InviteCode>> childrenMap = inviteCodes.stream().filter(ic -> ic.getCreatedBy() != null).collect(Collectors.groupingBy(InviteCode::getCreatedBy));// 修改 buildTree 方法
private InviteCodeTreeDTO buildTree(InviteCode root, Map<Integer, InviteCode> inviteCodeMap, Map<Integer, List<InviteCode>> childrenMap) {InviteCodeTreeDTO node = new InviteCodeTreeDTO();node.setId(root.getId());node.setInviteCode(root.getInviteCode());node.setInviteLevel(root.getInviteLevel());node.setChildren(new ArrayList<>());// 直接从 childrenMap 获取子节点List<InviteCode> children = childrenMap.getOrDefault(root.getId(), Collections.emptyList());for (InviteCode child : children) {InviteCodeTreeDTO childNode = buildTree(child, inviteCodeMap, childrenMap);node.getChildren().add(childNode);}return node;
}
- 效果:通过
childrenMap,可以以O(1)的时间复杂度找到某个id的所有子节点。
2. 并row处理
如果 inviteCodeMap 非常大,可以使用 parallelStream() 提高性能:
List<InviteCode> children = inviteCodeMap.values().parallelStream().filter(ic -> Objects.equals(ic.getCreatedBy(), root.getId())).collect(Collectors.toList());
- 注意:并行流适合大数据量,但在小数据量下可能有性能开销。
3. 日志记录
添加日志,跟踪子节点的查找:
List<InviteCode> children = inviteCodeMap.values().stream().filter(ic -> Objects.equals(ic.getCreatedBy(), root.getId())).collect(Collectors.toList());
logger.info("Found {} children for root node {}: {}", children.size(), root.getId(), children);
- 效果:便于调试和监控。
📝 完整代码:实际应用
以下是完整的 InviteCodeService 实现,展示了如何使用 children 构建层级树:
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);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) {InviteCodeTreeDTO node = new InviteCodeTreeDTO();node.setId(root.getId());node.setInviteCode(root.getInviteCode());node.setInviteLevel(root.getInviteLevel());node.setChildren(new ArrayList<>());// 直接从 childrenMap 获取子节点List<InviteCode> children = childrenMap.getOrDefault(root.getId(), Collections.emptyList());logger.info("Found {} children for root node {}: {}", children.size(), root.getId(), children);for (InviteCode child : children) {InviteCodeTreeDTO childNode = buildTree(child, inviteCodeMap, childrenMap);node.getChildren().add(childNode);}return node;}
}
🎉 总结
通过 Stream API 和 Collectors.toList,我们可以轻松地从 Map<Integer, InviteCode> 中筛选出子节点,为后续的层级树构建提供了基础。💻
- 核心代码:
inviteCodeMap.values().stream().filter(ic -> Objects.equals(ic.getCreatedBy(), root.getId())).collect(Collectors.toList())筛选出子节点。 - 优势:代码简洁、功能强大、支持并行处理。
- 优化:通过
childrenMap提高查找效率、添加日志、支持并行流。
希望这篇博客对你理解 Stream API 和 filter 操作有所帮助!💬 如果你有其他问题,欢迎留言讨论!🚀
📚 参考:Java 官方文档、Collectors 源码。点赞和分享哦!😊

