深入解析 Java Stream API:筛选根节点的优雅实现!!!

🚀 深入解析 Java Stream API:筛选根节点的优雅实现 🔧

大家好!👋 今天我们来聊聊 Java 8 中一个非常常见的操作:使用 Stream API 从 List 中筛选出特定条件的元素。🎉 具体来说,我们将深入分析以下代码片段:

java 复制代码
List<InviteCode> roots = inviteCodes.stream()
                .filter(ic -> ic.getCreatedBy() == null)
                .collect(Collectors.toList());

这段代码看似简单,但背后涉及了 Stream API、Lambda 表达式以及 Collectors.toList 的强大功能。💡 我们将从代码的背景开始,逐步拆解它的实现原理,探讨使用场景、优势和优化方法,最后通过一个实际案例展示它的应用。为了更直观地理解整个过程,我们还会插入一个 Mermaid 流程图!📊

准备好了吗?让我们开始吧!🚀


📖 背景:为什么需要筛选根节点?

在 Java 开发中,我们经常需要处理层级数据。例如,在一个邀请码系统中,我们有一个 List<InviteCode>,其中 InviteCode 是一个实体类,包含以下字段:

java 复制代码
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 == nullInviteCode 对象。换句话说:

  • createdBy == null:表示这是一个根节点(没有父节点)。
  • createdBy != null:表示这是一个子节点(有父节点,createdBy 是父节点的 id)。

因此,我们需要从 List<InviteCode> 中筛选出所有 createdBy == nullInviteCode 对象,这就是以下代码的作用:

java 复制代码
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 == nullInviteCode),丢弃不满足条件的元素。

类型Predicate<InviteCode>,将 InviteCode 映射为一个布尔值(truefalse)。

3. .collect(Collectors.toList())

  • collect :是 Stream API 的终止操作,用于将流中的元素收集到一个结果容器中(例如 ListSetMap)。
  • 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 == nullInviteCode 对象。
    • 将筛选结果收集到一个新的 List<InviteCode> 中。
  • 赋值 :将结果赋值给 rootsroots 是一个 List<InviteCode>,包含所有根节点。

📊 Mermaid 流程图:可视化筛选过程

为了更直观地理解从 List<InviteCode> 筛选根节点的过程,我们使用 Mermaid 流程图来展示:
True False Start: List<InviteCode> inviteCodes Stream<InviteCode>
inviteCodes.stream() For each InviteCode in Stream Check Condition:
ic.getCreatedBy() == null Keep InviteCode
(e.g., id=20, createdBy=null) Discard InviteCode
(e.g., id=21, createdBy=20) Collect to List<InviteCode>
Collectors.toList() End: List<InviteCode> roots

  • 流程说明
    1. List<InviteCode> 开始,转换为 Stream<InviteCode>
    2. 对流中的每个 InviteCode 对象:
      • 检查 createdBy == null
      • 如果 true,保留该对象;如果 false,丢弃。
    3. 将筛选后的元素收集到 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 = 20createdBy = null,保留。
    • id = 21createdBy = 20,丢弃。
    • id = 22createdBy = 20,丢弃。
    • id = 23createdBy = 20,丢弃。
    • id = 24createdBy = null,保留。
    • id = 25createdBy = 24,丢弃。
    • id = 26createdBy = 25,丢弃。
    • id = 27createdBy = 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 为根的层级树。层级树的构建需要从根节点开始,递归地查找子节点:

java 复制代码
// 找到所有根节点(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, 23createdBy = 20)。
    • id = 24 是第二个根节点(createdBy = null),它的子节点是 id = 25createdBy = 24),以此类推。
  • 筛选根节点的作用
    • 层级树的构建需要从根节点开始,createdBy == null 表示这是一个根节点(没有父节点)。

🚀 优势:为什么使用 Stream API?

1. 代码简洁

  • Stream API 提供了声明式的写法,比传统的 for 循环更简洁。

  • 传统写法可能需要手动遍历和填充 List

    java 复制代码
    List<InviteCode> roots = new ArrayList<>();
    for (InviteCode ic : inviteCodes) {
        if (ic.getCreatedBy() == null) {
            roots.add(ic);
        }
    }
  • 使用 Stream API,代码更简洁优雅。

2. 功能强大

  • Stream API 支持链式操作,可以轻松添加其他过滤条件。

  • 例如,如果只想筛选 inviteLevel == 0 的根节点:

    java 复制代码
    List<InviteCode> roots = inviteCodes.stream()
            .filter(ic -> ic.getCreatedBy() == null && ic.getInviteLevel() == 0)
            .collect(Collectors.toList());

3. 并行处理

  • Stream API 支持并行处理(parallelStream()),在大规模数据下可以提高性能:

    java 复制代码
    List<InviteCode> roots = inviteCodes.parallelStream()
            .filter(ic -> ic.getCreatedBy() == null)
            .collect(Collectors.toList());

🛠️ 优化建议

1. 添加日志

可以在筛选根节点后添加日志,方便调试:

java 复制代码
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() 提高性能:

java 复制代码
List<InviteCode> roots = inviteCodes.parallelStream()
        .filter(ic -> ic.getCreatedBy() == null)
        .collect(Collectors.toList());
  • 注意:并行流适合大数据量,但在小数据量下可能反而更慢(因为线程开销)。

3. 空列表处理

如果 roots 为空,可以提前返回:

java 复制代码
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 构建层级树:

java 复制代码
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 源码。点赞和分享哦!😊

相关推荐
若鱼191928 分钟前
gradlew在gitlab ci没可执行权限-permission denied
java
Linging_242 小时前
Springboot集成Debezium监听postgresql变更
java·spring boot·后端
小胖墩有点瘦2 小时前
基于SpringBoot的汽车租赁系统
java·spring boot
kill bert2 小时前
第29周 面试题精讲(4)
java·开发语言
橘猫云计算机设计2 小时前
基于springboot的电影院管理系统(源码+lw+部署文档+讲解),源码可白嫖!
java·spring boot·后端·小程序·java-ee·毕业设计
lili-felicity2 小时前
走进Java:String字符串的基本使用
java·开发语言
微wx笑3 小时前
当AI重构编程范式:Java 24的进化逻辑与技术深水区的战略突围
java·人工智能·重构
我要学编程(ಥ_ಥ)3 小时前
初始JavaEE篇 —— Mybatis操作数据库(上)
java·数据库·spring boot·java-ee·mybatis
m0_748256563 小时前
Windows 配置 Tomcat环境
java·windows·tomcat
my_realmy3 小时前
Java 之「单调栈」:从入门到实战
java·大数据·开发语言·ide·python