大家好! 今天我们来聊聊 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
是一个实体类,包含以下字段:
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
,包含 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
中筛选出所有 createdBy == null
的 InviteCode
对象,这就是以下代码的作用:
List<InviteCode> roots = inviteCodes.stream()
.filter(ic -> ic.getCreatedBy() == null)
.collect(Collectors.toList());
让我们逐步拆解这段代码,弄清楚它是如何工作的!
inviteCodes.stream()
inviteCodes
:是一个 List
,包含 adminId = 7
的 8 条记录(id = 20, 21, ..., 27
)。stream()
:将 List
转换为一个 Stream
。
Stream
是 Java 8 引入的流式 API,允许你以声明式的方式处理集合数据(例如映射、过滤、归约等)。结果:inviteCodes.stream()
生成了一个 Stream
,包含 8 个 InviteCode
对象。
.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
映射为一个布尔值(true
或 false
)。
.collect(Collectors.toList())
collect
:是 Stream API 的终止操作,用于将流中的元素收集到一个结果容器中(例如 List
、Set
或 Map
)。Collectors.toList()
:是一个收集器(Collector),专门用于将流中的元素收集到一个 List
中。结果:collect(Collectors.toList())
将筛选后的 Stream
收集到一个新的 List
中。
inviteCodes.stream().filter(ic -> ic.getCreatedBy() == null).collect(Collectors.toList())
:
inviteCodes
创建一个 Stream
。createdBy == null
的 InviteCode
对象。List
中。roots
,roots
是一个 List
,包含所有根节点。为了更直观地理解从 List
筛选根节点的过程,我们使用 Mermaid 流程图来展示:
List
开始,转换为 Stream
。InviteCode
对象:
createdBy == null
。true
,保留该对象;如果 false
,丢弃。List
中。假设 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 |
inviteCodes.stream()
Stream
,包含 8 个 InviteCode
对象(id = 20, 21, ..., 27
)。.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
只包含 2 个元素:
InviteCode(id=20, createdBy=null, ...)
。InviteCode(id=24, createdBy=null, ...)
。.collect(Collectors.toList())
Stream
收集到一个新的 List
中。结果:roots
是一个 List
,包含以下 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
表示这是一个根节点(没有父节点)。for
循环更简洁。List
:List<InviteCode> roots = new ArrayList<>();
for (InviteCode ic : inviteCodes) {
if (ic.getCreatedBy() == null) {
roots.add(ic);
}
}
inviteLevel == 0
的根节点:List<InviteCode> roots = inviteCodes.stream()
.filter(ic -> ic.getCreatedBy() == null && ic.getInviteLevel() == 0)
.collect(Collectors.toList());
parallelStream()
),在大规模数据下可以提高性能:List<InviteCode> roots = inviteCodes.parallelStream()
.filter(ic -> ic.getCreatedBy() == null)
.collect(Collectors.toList());
可以在筛选根节点后添加日志,方便调试:
List<InviteCode> roots = inviteCodes.stream()
.filter(ic -> ic.getCreatedBy() == null)
.collect(Collectors.toList());
logger.info("Found {} root nodes for adminId {}: {}", roots.size(), adminId, roots);
如果 inviteCodes
非常大,可以使用 parallelStream()
提高性能:
List<InviteCode> roots = inviteCodes.parallelStream()
.filter(ic -> ic.getCreatedBy() == null)
.collect(Collectors.toList());
如果 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 转换为 Map
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
中筛选出根节点,为后续的层级树构建提供了基础。
inviteCodes.stream().filter(ic -> ic.getCreatedBy() == null).collect(Collectors.toList())
筛选出根节点。希望这篇博客对你理解 Stream API 和 filter
操作有所帮助! 如果你有其他问题,欢迎留言讨论!
参考:Java 官方文档、Collectors
源码。点赞和分享哦!