构建树工具类

简介: 在系统设计中,有许多地方都需要用到树型结构,所以建立一个构建树的工具类,封装关于树操作的一些通用方法,方便使用。

1、应用场景

      在系统设计中,有许多地方都需要用到树型结构,最常见的场景就是电商系统中的商品分类,我们需要将数据库中存储的商品分类取出来并转换为树结构,也需要通过类别的id去过滤出该类别及子类别下的商品信息,这种功能要如何实现呢,所以建立一个构建树工具类,封装构建树的通用方法,方便使用。

2、构建树方法

2.1 代码实现

publicstatic<T>List<T>buildTree(List<T>nodes, Function<T, String>key, Function<T, String>parentKey, Function<T, List<T>>getChildren, BiConsumer<T, List<T>>setChildren) {
List<T>tree=newArrayList<>(16);
Map<String, T>idNodeMap=nodes.stream().collect(Collectors.toMap(key, e->e, (a, b) ->a, LinkedHashMap::new));
idNodeMap.forEach((k, v) -> {
StringparentId=parentKey.apply(v);
if (ROOT_NODE.equals(parentId)) {
tree.add(v);
            } else {
Tt=idNodeMap.get(parentId);
if (ObjectUtil.isNotEmpty(t)) {
List<T>children=getChildren.apply(t);
if (CollectionUtil.isEmpty(children)) {
children=newArrayList<>(16);
setChildren.accept(t, children);
                    }
children.add(v);
                } else {
tree.add(v);
                }
            }
        });
returntree;
    }

2.2 代码讲解

      创建静态方法 buildTree(),这里需要传递五个参数,List<T> nodes:节点列表,Function<T, String> key:获取 key 的函数接口,Function<T, String> parentKey: 获取父 key 的函数接口,Funcation<T, List<T>> getChildren: 获取子节点集合的函数接口,BiConsumer<T, List<T>> setChildren: 设置子节点集合的函数接口。

      方法体中首先定义一个 list 集合并设置初始容量为 16,用于存储树型对象,树型对象一般包含三个属性:节点 id、节点名称和子节点集合。接着通过 stream 流的方式将 nodes集合转为 map, key 在这里可以视为类别的 id,T 可以看作是类别实体对象。

      接着遍历这个 map 集合,通过传入的 parentKey 函数接口,然后调用 apply(v)就可以获取对应的 parentId,然后对 parentId 与根节点标识进行比较,如果相同就直接加入集合中,此时视为顶级类别。如果不同的话,从 map 中获取 parentId 对应的值,如果获取为空,直接加入集合中作为顶级类别,如果不为空的话,就需要通过传入的 getChildren 函数接口,获取该节点父类别中的子节点集合,如果该集合为空,说明当前节点为父类别中的第一个子节点,则需要创建一个 children 集合,调用 setChildren 函数将 children 集合设置为父类别的子节点集合,然后将当前节点加入集合中,如果该集合不为空,则直接将当前节点加入到该集合中即可。通过上诉过程的调用,就可以将一个对象集合转化为树型结构.

3、构建过滤树,排除符合exclude的节点

3.1 代码实现

publicstatic<T>voidexcludeTree(List<T>tree, Function<T, List<T>>getChildren, Predicate<T>exclude) {
tree.removeIf(e-> {
List<T>children=getChildren.apply(e);
if (CollectionUtil.isNotEmpty(children)) {
excludeTree(children, getChildren, exclude);
            }
returnexclude.test(e);
        });
    }

3.2 代码讲解

函数的输入参数包括一个树结构列表tree,一个获取子节点的函数getChildren,和一个判断节点是否需要排除的条件exclude。  

函数的作用是遍历树结构,对每个节点进行判断,如果满足排除条件,则将该节点从树中移除。对于每个节点,还会递归调用excludeTree函数,对其子节点进行相同的处理。  

具体步骤如下:  

1. 使用Lambda表达式定义一个匿名函数e,作为tree.removeIf的参数。该函数的作用是对树结构中的每个节点进行判断和处理。  

2. 在函数内部,首先使用getChildren函数获取当前节点的子节点列表children。  

3. 如果children不为空,则递归调用excludeTree函数,对children进行相同的处理。  

4. 最后,使用exclude函数对当前节点进行判断。如果满足排除条件,则返回true,表示需要将该节点从树中移除;否则返回false,表示保留该节点。

4、根据树以及节点id获取该节点及以下的节点id

4.1 代码实现

publicstatic<T>List<String>getTreeIds(List<T>tree, StringnodeId, Function<T, String>key,  Function<T, List<T>>getChildren) {
if (ObjectUtils.isEmpty(tree) ||StringUtils.isBlank(nodeId)) {
returnnewArrayList<>();
        }
List<String>nodeIds=newArrayList<>();
returngetTreeIds(tree, nodeId, nodeIds, key, getChildren);
    }
publicstatic<T>List<String>getTreeIds(List<T>tree, StringnodeId, List<String>nodeIds, Function<T, String>key, Function<T, List<T>>getChildren) {
if (CollectionUtil.isNotEmpty(tree)) {
tree.forEach(o-> {
if (StringUtils.equals(key.apply(o), nodeId)) {
nodeIds.add(key.apply(o));
getTreeIds(getChildren.apply(o), nodeIds, key, getChildren);
                }
getTreeIds(getChildren.apply(o), nodeId, nodeIds, key, getChildren);
            });
        }
returnnodeIds;
    }
publicstatic<T>voidgetTreeIds(List<T>tree, List<String>nodeIds, Function<T, String>key, Function<T, List<T>>getChildren) {
if(ObjectUtils.isNotEmpty(tree)){
tree.forEach(o-> {
nodeIds.add(key.apply(o));
if (ObjectUtils.isNotEmpty(getChildren.apply(o))) {
getTreeIds(getChildren.apply(o), nodeIds, key, getChildren);
                }
            });
        }
    }

4.2 代码讲解

这段代码用于获取树形结构数据中指定节点及其子节点的id列表。它通过递归遍历树形结构,找到目标节点并将其id添加到列表中,然后继续遍历目标节点的子节点,重复上述操作,直到遍历完所有节点。最终返回包含目标节点及其子节点id的列表。

相关文章
|
Web App开发 存储 网络协议
chrome命令行参数
chrome命令行参数
381 0
|
Linux iOS开发 MacOS
typora下载和破解(仅供学习)
Typora 一款 Markdown 编辑器和阅读器 风格极简 / 多种主题 / 支持 macOS,Windows 及 Linux 实时预览 / 图片与文字 / 代码块 / 数学公式 / 图表 目录大纲 / 文件管理 / 导入与导出 ……
162828 11
typora下载和破解(仅供学习)
|
计算机视觉
轻松编写高效工具类:构建树形结构的秘籍
轻松编写高效工具类:构建树形结构的秘籍
532 0
|
8月前
|
SQL Java 数据库连接
如何在 Java 代码中使用 JSqlParser 解析复杂的 SQL 语句?
大家好,我是 V 哥。JSqlParser 是一个用于解析 SQL 语句的 Java 库,可将 SQL 解析为 Java 对象树,支持多种 SQL 类型(如 `SELECT`、`INSERT` 等)。它适用于 SQL 分析、修改、生成和验证等场景。通过 Maven 或 Gradle 安装后,可以方便地在 Java 代码中使用。
2598 11
|
SQL 人工智能 Java
mybatis-plus配置sql拦截器实现完整sql打印
_shigen_ 博主分享了如何在MyBatis-Plus中打印完整SQL,包括更新和查询操作。默认日志打印的SQL用?代替参数,但通过自定义`SqlInterceptor`可以显示详细信息。代码示例展示了拦截器如何替换?以显示实际参数,并计算执行时间。配置中添加拦截器以启用此功能。文章提到了分页查询时的限制,以及对AI在编程辅助方面的思考。
1918 5
mybatis-plus配置sql拦截器实现完整sql打印
|
存储 消息中间件 JSON
DDD基础教程:一文带你读懂DDD分层架构
DDD基础教程:一文带你读懂DDD分层架构
|
存储 运维 Shell
Ansible自动化运维工具安装和基本使用
Ansible 是一款无代理的IT自动化工具,通过SSH连接目标主机执行配置管理、应用部署和云端管理任务。它使用YAML编写的Playbook定义任务,核心组件包括Playbook、模块、主机清单、变量等。Ansible的优势在于易用、功能强大、无须在目标主机安装额外软件,并且开源。安装过程涉及配置网络源、yum安装和SSH密钥设置。通过定义主机清单和使用模块进行通信测试,确保连接成功。
424 2
Ansible自动化运维工具安装和基本使用
|
Java 开发者 Spring
springboot @Primary的概念与使用
【4月更文挑战第26天】在 Spring Framework 中,@Primary 注解用于标记一个 bean 作为在多个同类型的 bean 候选中进行自动装配时的首选 bean。这个注解非常有用,在配置和自动装配复杂的 Spring 应用程序时尤其如此,特别是当有多个 bean 实现相同的接口或继承相同的类时
1208 3
|
运维 监控 前端开发
[SpringAop + Logback +MDC] 现网必备全链路日志追踪
[SpringAop + Logback +MDC] 现网必备全链路日志追踪
1668 1
|
JavaScript 前端开发 开发者
DOMException: 属性设置错误处理指南
DOMException: 属性设置错误处理指南
517 0