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

相关推荐
zhojiew1 分钟前
istio in action之流量控制与路由
java·数据库·istio
D_aniel_16 分钟前
排序算法-归并排序
java·排序算法·归并排序
可儿·四系桜1 小时前
WebSocket:实时通信的新时代
java·网络·websocket·网络协议
forestsea1 小时前
Maven 插件机制与生命周期管理
java·maven
七月在野,八月在宇,九月在户1 小时前
maven 依赖冲突异常分析
java·maven
金融数据出海1 小时前
黄金、碳排放期货市场API接口文档
java·开发语言·spring boot·后端·金融·区块链
胡斌附体1 小时前
微服务中 本地启动 springboot 无法找到nacos配置 启动报错
java·spring boot·微服务·yml·naocs yml
薯条不要番茄酱2 小时前
【JVM】从零开始深度解析JVM
java·jvm
夏季疯2 小时前
学习笔记:黑马程序员JavaWeb开发教程(2025.3.31)
java·笔记·学习
D_aniel_2 小时前
排序算法-快速排序
java·排序算法·快速排序