深入解析 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
源码。点赞和分享哦!😊