当前位置: 首页 > news >正文

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

🚀 深入解析 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 Setters
    public 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 的所有邀请码记录:

idadmin_idcreated_byinvite_codeinvite_level
207NULL******0
217202631131
227207043581
237209828681
247NULL******0
25724******1
26725******2
277269914763

我们的目标是构建一个以 adminId 为根的邀请码层级树。层级树的构建需要递归地从根节点开始,找到每个节点的子节点。子节点的定义是 createdBy 等于当前节点 idInviteCode 对象。换句话说:

  • 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>,键是 InviteCodeid,值是 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():获取当前根节点的 idInteger 类型)。
    • Objects.equals(ic.getCreatedBy(), root.getId()):比较 ic.getCreatedBy()root.getId() 是否相等。
      • 使用 Objects.equals 而不是直接 == 是为了安全地处理 null 值(如果 ic.getCreatedBy()null== 可能会导致问题)。
  • 作用filter 会保留所有满足条件的元素(createdBy 等于 root.getId()InviteCode),丢弃不满足条件的元素。

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

4. .collect(Collectors.toList())

  • collect:是 Stream API 的终止操作,用于将流中的元素收集到一个结果容器中(例如 ListSetMap)。
  • 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> 中。
  • 赋值:将结果赋值给 childrenchildren 是一个 List<InviteCode>,包含 root 的所有直接子节点。

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

为了更直观地理解从 Map<Integer, InviteCode> 筛选子节点的过程,我们使用 Mermaid 流程图来展示:

True
False
Start: Map<Integer, InviteCode> inviteCodeMap
Collection<InviteCode>
inviteCodeMap.values()
Stream<InviteCode>
.stream()
For each InviteCode in Stream
Check Condition:
Objects.equals(ic.getCreatedBy(), root.getId())
Keep InviteCode
(e.g., id=21, createdBy=20)
Discard InviteCode
(e.g., id=24, createdBy=null)
Collect to List<InviteCode>
Collectors.toList()
End: List<InviteCode> children
  • 流程说明
    1. Map<Integer, InviteCode> 开始,获取所有值(inviteCodeMap.values())。
    2. 转换为 Stream<InviteCode>
    3. 对流中的每个 InviteCode 对象:
      • 检查 createdBy == root.getId()
      • 如果 true,保留该对象;如果 false,丢弃。
    4. 将筛选后的元素收集到 List<InviteCode> 中。

📝 示例:具体数据

假设 inviteCodeMap 包含以下数据(adminId = 7):

idadmin_idcreated_byinvite_codeinvite_level
207NULL******0
217202631131
227207043581
237209828681
247NULL******0
25724******1
26725******2
277269914763

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()))

假设当前 rootid = 20InviteCode

  • root.getId() = 20
  • 对每个 InviteCode 对象检查 createdBy 是否等于 20
    • id = 20createdBy = nullObjects.equals(null, 20) = false,丢弃。
    • id = 21createdBy = 20Objects.equals(20, 20) = true,保留。
    • id = 22createdBy = 20Objects.equals(20, 20) = true,保留。
    • id = 23createdBy = 20Objects.equals(20, 20) = true,保留。
    • id = 24createdBy = nullObjects.equals(null, 20) = false,丢弃。
    • id = 25createdBy = 24Objects.equals(24, 20) = false,丢弃。
    • id = 26createdBy = 25Objects.equals(25, 20) = false,丢弃。
    • id = 27createdBy = 26Objects.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 = 20createdBy = null,丢弃。
    • id = 21createdBy = 20,丢弃。
    • id = 22createdBy = 20,丢弃。
    • id = 23createdBy = 20,丢弃。
    • id = 24createdBy = null,丢弃。
    • id = 25createdBy = 24,保留。
    • id = 26createdBy = 25,丢弃。
    • id = 27createdBy = 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 = 20InviteCode,即 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 源码。点赞和分享哦!😊

在这里插入图片描述

相关文章:

  • 金桔网桥路由版3
  • Excel(进阶篇):powerquery详解、PowerQuery的各种用法,逆透视表格、双行表头如何制作透视表、不规则数据如何制作数据透视表
  • 网络华为HCIA+HCIP 网络编程自动化
  • 《Python实战进阶》No26: CI/CD 流水线:GitHub Actions 与 Jenkins 集成
  • Neo4j GDS(Graph Data Science)库安装(Mac版)
  • 网络知识编-数据链路层(以太网 局域网通信 ARP协议 ARP 欺骗 DDos 攻击)
  • Elasticsearch零基础入门:从安装到入门
  • 紫光展锐社招
  • 详解MySQL的事务实现机制
  • 网页性能优化中 有一条叫做“避免使用未合成的动画”,请问该如何理解?
  • 【技术选型】三大 Python Web 框架全面对比
  • SQL Server数据库表删除分区
  • self Attention为何除以根号dk?(全新角度)
  • 【入门初级篇】布局类组件的使用(2)
  • IRF拆除
  • android facebook 分享到 messenger
  • XSS跨站脚本攻击漏洞(Cross Site Scripting)
  • Java 的 正则表达式
  • 网络爬虫【爬虫库request】
  • QT编程之数据库开发
  • 秦洪看盘|交易新逻辑,银行股成A股稳定器
  • 反制美国钢铝关税!印度拟对美国部分商品征收关税
  • 专访|导演刘江:给谍战题材注入现实主义的魂
  • 多地警务新媒体整合:关停交警等系统账号,统一信息发布渠道
  • 从普通人经历中发现历史,王笛解读《线索与痕迹》
  • 英媒:英国首相住所起火,目前无人伤亡