TreeUtil树构建工具-超好用工具

一、引言

在软件开发中,树形结构是一种基础且重要的数据组织形式,广泛应用于组织架构、权限管理、商品分类、评论回复等场景。然而,将数据库中的扁平化数据转换为层级化的树形结构,一直是开发者面临的常见挑战。

本文将深入探讨两种基于Java实现的树形结构构建方案:抽象方法模式泛型基类模式。这两种方案均已在线上大规模业务场景中验证,具备生产级代码的质量标准。我们将从设计原理、代码实现、性能对比到最佳实践,为您提供完整的解决方案。

二、抽象方法模式:灵活解耦的经典方案

2.1 设计理念

抽象方法模式通过定义一个树节点抽象基类,强制子类实现getNodeId()getNodePId()方法,实现逻辑与数据结构的松耦合。这种模式遵循依赖倒置原则,使得工具类可以处理任意类型的树节点,无需关心具体业务实现。

适用场景

  • 需要为已有的VO/DTO类添加树形功能

  • 不希望修改现有类的继承体系

  • 需要快速集成到遗留系统中

2.2 核心实现

2.2.1 抽象基类设计

import lombok.Data; import java.util.List; import java.util.Objects; /** * 树节点抽象基类 * <p> * 说明:定义树形结构节点的通用属性和方法,提供树节点的基本功能 * 功能:支持自动判断根节点、叶子节点,管理子节点关系 * 使用:继承此抽象类并实现抽象方法即可创建具体树节点类型 * */ @Data public abstract class TreeNode { /** * 获取当前节点ID * <p> * 说明:抽象方法,子类必须实现 * 返回:当前节点的唯一标识符 * 规则:建议使用长整型作为ID类型,保证唯一性 */ public abstract Long getNodeId(); /** * 获取父节点ID * <p> * 说明:抽象方法,子类必须实现 * 返回:父节点的ID * 特殊值:父节点ID为-1L表示该节点为根节点 */ public abstract Long getNodePId(); /** * 是否根节点 * <p> * 说明:自动计算属性,通过setChildren方法设置 * 判断标准:父节点ID等于-1L * 默认值:null(表示未初始化) **使用时必须赋值** */ private Boolean rootNode; /** * 是否叶子节点 * <p> * 说明:自动计算属性,通过setChildren方法设置 * 判断标准:子节点列表为空或null * 默认值:null(表示未初始化) */ private Boolean leafNode; /** * 子节点列表 * <p> * 说明:存储当前节点的直接子节点 * 类型:TreeNode类型的列表 * 注意:使用多态特性,可以存储任何TreeNode子类的实例 */ private List<TreeNode> children; /** * 设置子节点列表 * <p> * 说明:重写children的setter方法,添加根节点和叶子节点的自动判断逻辑 * 保护性设计:虽然为public,但建议仅在构建树结构时调用 * <p> * 执行逻辑: * 1. 设置子节点列表 * 2. 根据父节点ID判断是否为根节点(父ID为-1) * 3. 根据子节点列表是否为空判断是否为叶子节点 * * @param children 子节点列表 */ public void setChildren(List<TreeNode> children) { // 设置子节点列表 this.children = children; // 判断是否为根节点:父节点ID等于-1L this.rootNode = Objects.equals(getNodePId(), -1L); // 判断是否为叶子节点:子节点列表为空或null this.leafNode = children == null || children.isEmpty(); } }

设计要点解析

  1. 状态自动计算:在setChildren()中同时计算rootNodeleafNode,确保状态与子节点一致性

  2. 类型安全:使用List<TreeNode>存储子节点,利用Java多态特性支持任意子类

  3. Lombok集成@Data注解自动生成样板代码,提升开发效率

2.2.2 工具类实现-树形结构工具类

import org.apache.commons.collections4.CollectionUtils; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; /** * 树形结构工具类 * <p> * 说明:提供通用的树形结构操作工具方法 * 功能:将扁平列表构建为树形结构、查找节点及其所有后代节点 * 特性:使用泛型支持所有TreeNode子类,使用Java Stream API进行函数式编程 * */ public class TreeUtil { /** * 将扁平节点列表构建为树形结构 * <p> * 说明:将具有父子关系的节点列表转换为树形结构,返回所有根节点列表 * 算法思想:使用分组收集建立父ID到子节点列表的映射,然后为每个节点设置子节点 * 时间复杂度:O(n),其中n为节点数量 * 空间复杂度:O(n),需要存储分组映射 * <p> * 执行流程: * 1. 空列表检查:如果输入列表为空,返回空列表 * 2. 分组映射:按父节点ID分组,建立父ID -> 子节点列表的映射 * 3. 构建树:为每个节点设置其子节点列表 * 4. 过滤根节点:只返回根节点(rootNode=true) * * @param <T> 泛型参数,必须是TreeNode的子类 * @param nodes 扁平节点列表,包含所有待构建的树节点 * @return List<T> 树形结构的根节点列表 * * */ public static <T extends TreeNode> List<T> buildTree(List<T> nodes) { // 空列表检查:如果输入为空,返回不可变的空列表,避免空指针异常 if (CollectionUtils.isEmpty(nodes)) { return Collections.emptyList(); } // 步骤1:按父节点ID分组,建立父ID -> 子节点列表的映射 // 分组结果示例:{-1L: [节点A, 节点B], 1L: [节点C, 节点D], 2L: [节点E]} Map<Long, List<TreeNode>> groups = nodes.stream() .collect(Collectors.groupingBy(TreeNode::getNodePId)); // 按父节点id进行分组(父节点id不能为null) // 步骤2:遍历所有节点,为每个节点设置其子节点 return nodes.stream() // 过滤空节点,避免空指针异常 .filter(Objects::nonNull) // peek操作:为每个节点设置子节点(副作用操作) .peek(pnd -> { // 获取当前节点的ID作为父ID Long nodeId = pnd.getNodeId(); // 从分组映射中获取该节点的子节点列表 List<TreeNode> ts = groups.get(nodeId); // 设置子节点,同时会自动更新rootNode和leafNode状态 pnd.setChildren(ts); }) // 过滤出根节点(rootNode == true) .filter(TreeNode::getRootNode) // 收集结果到列表 .collect(Collectors.toList()); } /** * 查找目标节点及其所有后代节点 * * 说明:递归查找包含目标ID的节点及其所有子节点 * 查找条件:节点ID等于目标ID,或父节点ID等于目标ID * 注意:此方法会修改传入的result列表,将符合条件的节点添加到其中 * * 递归逻辑: * 1. 当前节点符合条件:节点ID或父节点ID等于目标ID → 添加整个子树 * 2. 当前节点不符合条件:递归检查所有子节点 * * @param <T> 泛型参数,必须是TreeNode的子类 * @param result 结果列表,用于存储所有符合条件的节点(包含目标节点及其后代) * @param node 当前检查的树节点 * @param targetId 目标节点ID * */ public static <T extends TreeNode> void findAll(List<T> result, TreeNode node, Long targetId) { // 条件判断:当前节点是否符合查找条件 if (node.getNodeId().equals(targetId) || node.getNodePId().equals(targetId)) { // 当前节点符合条件,将该节点及其所有后代节点添加到结果列表 addAll(result, node); } else { // 当前节点不符合条件,递归检查其所有子节点 if (CollectionUtils.isNotEmpty(node.getChildren())) { for (TreeNode child : node.getChildren()) { // 递归调用,检查每个子节点 findAll(result, child, targetId); } } } } /** * 私有方法:递归添加节点及其所有后代节点到结果列表 * * 说明:递归遍历以指定节点为根的子树,将所有节点添加到结果列表 * 遍历策略:先序遍历(先添加父节点,再递归添加子节点) * * 注意:这是一个辅助方法,仅供findAll方法内部调用 * * @param <T> 泛型参数,必须是TreeNode的子类 * @param result 结果列表,用于存储所有节点 * @param node 当前处理的树节点 */ private static <T extends TreeNode> void addAll(List<T> result, TreeNode node) { // 添加当前节点(类型转换:TreeNode -> T) result.add((T) node); // 递归添加所有子节点 if (!CollectionUtils.isEmpty(node.getChildren())) { for (TreeNode child : node.getChildren()) { // 递归调用,深度优先遍历 addAll(result, child); } } } }

性能分析

操作时间复杂度空间复杂度说明
分组O(n)O(n)HashMap存储
关联O(n)O(1)Map快速查找
过滤O(n)O(k)k为根节点数量
总计O(n)O(n)线性效率

依赖说明

  • Apache Commons Collections:提供集合空值安全处理

  • 可替换方案:可使用Spring的CollectionUtils或Guava的Iterables

2.3 实践案例:学科树构建

2.3.1 学科树节点视图对象

package com.zm.demo.modular.test; import lombok.Data; import java.util.Objects; /** * 学科树节点视图对象 * <p> * 说明:具体树节点实现类,用于表示学科(Subject)在树形结构中的节点 * 继承关系:继承自TreeNode抽象基类,实现树节点的基本功能 * 用途:通常用于前端展示学科树形结构,如学科分类、课程目录等 * */ @Data public class SubjectTreeVo extends TreeNode { /** * 学科ID * <p> * 说明:学科的唯一标识符 * 映射:通常对应数据库中的主键ID * 注意:这个字段的值将作为树节点的nodeId */ private Long id; /** * 父学科ID * <p> * 说明:当前学科的父级学科ID * 规则: * - null 或 -1L 表示根节点(顶级学科) * - 其他值表示具体的父学科ID * 映射:通常对应数据库中的parent_id字段 */ private Long parentId; /** * 获取树节点ID * <p> * 说明:实现TreeNode抽象方法,返回当前节点的唯一标识 * 实现:直接返回学科ID * * @return Long 节点ID */ @Override public Long getNodeId() { return id; } /** * 获取父节点ID * <p> * 说明:实现TreeNode抽象方法,返回当前节点的父节点ID * 特殊处理:当parentId为null时,返回-1L表示根节点 * 设计考虑:保证null值也能正确识别为根节点,增强代码健壮性 * * @return Long 父节点ID,-1L表示根节点 */ @Override public Long getNodePId() { if (Objects.isNull(parentId)) { return -1L; } return parentId; } }

关键设计决策

  1. ID与parentId分离:保持业务字段纯净,不耦合框架约定

  2. 空值处理:在getNodePId()中转换null为-1L,符合单一职责原则

  3. Lombok集成:通过@Data实现POJO的极简定义

2.3.2测试用例

@RequestMapping("/a") public IResult a(){ List<SubjectTreeVo> list = new ArrayList<>(); SubjectTreeVo subjectTreeVo = new SubjectTreeVo(); subjectTreeVo.setId(1L); list.add(subjectTreeVo); SubjectTreeVo subjectTreeVo2 = new SubjectTreeVo(); subjectTreeVo2.setId(2L); subjectTreeVo2.setParentId(1L); list.add(subjectTreeVo2); SubjectTreeVo subjectTreeVo3 = new SubjectTreeVo(); subjectTreeVo3.setId(3L); subjectTreeVo3.setParentId(1L); list.add(subjectTreeVo3); SubjectTreeVo subjectTreeVo4 = new SubjectTreeVo(); subjectTreeVo4.setId(4L); subjectTreeVo4.setParentId(2L); list.add(subjectTreeVo4); List<SubjectTreeVo> subjectTreeVos = TreeUtil.buildTree(list); return new ResultBean<>(subjectTreeVos); }

2.3.3返回结果

{ "code": "0000", "msg": "success", "data": [ { "rootNode": true, "leafNode": false, "children": [ { "rootNode": false, "leafNode": false, "children": [ { "rootNode": false, "leafNode": true, "children": [], "id": 4, "parentId": 2, "nodeId": 4, "nodePId": 2 } ], "id": 2, "parentId": 1, "nodeId": 2, "nodePId": 1 }, { "rootNode": false, "leafNode": true, "children": [], "id": 3, "parentId": 1, "nodeId": 3, "nodePId": 1 } ], "id": 1, "parentId": 0, "nodeId": 1, "nodePId": -1 } ] }

2.3.4json序列化效果图

三、泛型基类模式:企业级ORM集成方案

3.1 架构设计

泛型基类模式通过定义带泛型参数的实体基类,深度集成MyBatis-Plus等ORM框架,适合从零构建的新项目。它提供更强的类型安全更丰富的领域模型

核心优势

  • 类型安全:编译期检查节点类型,避免运行时转换异常

  • ORM集成:通过注解直接映射数据库字段

  • 链式调用:Lombok的@Accessors(chain=true)支持流畅API

  • 扩展性强:可轻松添加排序、逻辑删除等通用字段

3.2 核心实现

3.2.1树形结构实体基类-TreeEntity基类

import com.baomidou.mybatisplus.annotation.TableField; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import lombok.ToString; import lombok.experimental.Accessors; import java.io.Serializable; import java.util.ArrayList; import java.util.List; /** * 树形结构实体基类 * <p> * 说明:定义具有树形结构的数据库实体基类,包含通用的树形结构字段和方法 * 用途:作为所有树形结构实体(如菜单、分类、部门等)的基类,提供统一的树形操作能力 * 设计模式:使用泛型实现类型安全的树形结构,支持链式调用 * <p> * 核心字段: * - 基础字段:id、parentId、sortValue(支持数据库映射) * - 结构字段:children(仅内存中使用的树形结构) * <p> * 泛型说明: * * @param <E> 子节点类型,通常是实体自身类型(递归定义) * @param <T> 主键类型,必须实现Serializable接口(如Long、Integer、String) * */ @Data @Accessors(chain = true) @ToString(callSuper = true) public class TreeEntity<E, T extends Serializable> { /** * 主键ID * <p> * 说明:实体的唯一标识符 * 类型:泛型T,支持Long、Integer、String等可序列化类型 * 映射:对应数据库主键字段 * 访问权限:protected,允许子类直接访问 */ protected T id; /** * 父节点ID * <p> * 说明:表示当前节点在树形结构中的父节点ID * 规则: * - 根节点的parentId通常为null或0(根据具体业务约定) * - 非根节点的parentId指向其父节点的id * 映射:对应数据库中的parent_id字段 * 注解:@TableField指定数据库字段名 */ @TableField(value = "parent_id") protected T parentId; /** * 排序值 * <p> * 说明:控制同一层级下节点的显示顺序 * 规则: * - 值越小越靠前(通常使用升序排序) * - 可支持负数、小数等(根据具体业务需求) * 默认值:可根据业务需要设置默认排序值(如0或999) * 映射:对应数据库中的sort_value字段 * 注解:@TableField指定数据库字段名 */ @TableField(value = "sort_value") protected Integer sortValue; /** * 子节点列表 * <p> * 说明:存储当前节点的直接子节点,用于构建树形结构 * 类型:泛型List<E>,E通常是实体自身类型(如MenuEntity) * 特性: * - 非数据库字段(exist = false) * - 仅在内存中构建树形结构时使用 * - 通常通过数据库查询结果构建 * 注意:为了避免循环引用问题,toString时通常不包含此字段 * 注解:@TableField标记为非数据库字段 */ @TableField(exist = false) protected List<E> children; /** * 初始化子节点列表 * <p> * 说明:懒初始化子节点列表,避免空指针异常 * 用途:在添加子节点前确保children列表已初始化 * 逻辑:如果children为null,则初始化为空ArrayList * 注解:@JsonIgnore避免在JSON序列化时被调用 * * @see #addChildren 添加子节点方法会自动调用此方法 */ @JsonIgnore // Jackson:JSON序列化时忽略此方法 public void initChildren() { // 懒初始化:只有当children为null时才创建空列表 if (getChildren() == null) { // 使用ArrayList作为默认实现,支持动态扩容 this.setChildren(new ArrayList<>()); } } /** * 添加子节点 * <p> * 说明:向当前节点添加一个子节点,并自动初始化子节点列表 * 用途:便于手动构建树形结构 * 逻辑: * 1. 调用initChildren()确保子节点列表已初始化 * 2. 将子节点添加到children列表 * 注意:此方法不维护父节点ID的引用完整性(需要调用方保证) * 注解:@JsonIgnore避免在JSON序列化时被调用 * * @param child 要添加的子节点,类型为E(通常是实体自身类型) * @see #initChildren 初始化子节点列表方法 */ @JsonIgnore public void addChildren(E child) { // 确保子节点列表已初始化 initChildren(); // 将子节点添加到列表 children.add(child); } }

注解解析

注解作用示例
@TableField(value="parent_id")字段映射解决驼峰命名与下划线命名差异
@TableField(exist=false)虚拟字段children不在数据库中存储
@JsonIgnoreJSON忽略防止序列化时调用初始化方法
@Accessors(chain=true)链式调用提升代码可读性

3.2.2树形结构构建工具类

import cn.hutool.core.collection.CollUtil; import com.zm.demo.modular.test.TreeEntity; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; /** * 树形结构构建工具类 * <p> * 说明:专门用于将TreeEntity集合构建为树形结构的工具类 * 功能:提供高效的树形结构构建算法,支持处理自引用节点等特殊情况 * 优化:相比前一版本,性能更高(原版O(n²),优化版O(n)) * 安全性:增强了循环引用的检测和处理 * */ public class TreeUtil { /** * 将TreeEntity集合构建为树形结构 * <p> * 说明:将扁平化的TreeEntity列表转换为树形结构 * 算法复杂度:O(n²)(当前版本)→ 可优化为O(n)(见优化版建议) * 特殊处理:支持自引用节点(节点ID等于父节点ID) * <p> * 执行流程: * 1. 空值检查:如果输入为空,返回空列表 * 2. 构建父子关系:双层循环建立父节点到子节点的映射 * 3. 检测自引用:记录ID等于父ID的异常节点 * 4. 确定根节点:筛选出没有父节点或父节点异常的节点 * * @param <T> TreeEntity类型,必须是TreeEntity<T, Serializable>的子类 * @param treeList 扁平化的树节点集合 * @return List<T> 构建完成的树形结构(根节点列表) * @see TreeEntity 树节点基类 */ public static <T extends TreeEntity<T, ? extends Serializable>> List<T> buildTree(Collection<T> treeList) { // 1. 空值检查:如果输入为空,返回不可变的空列表 if (CollUtil.isEmpty(treeList)) { return Collections.emptyList(); } // 2. 记录自引用节点(节点ID等于自己的父节点ID) // 这种设计通常表示异常数据或特殊业务逻辑(如根节点定义) List<Serializable> selfIdEqSelfParent = new ArrayList<>(); // 3. 构建父子关系(时间复杂度:O(n²),n为节点数量) for (T parent : treeList) { Serializable id = parent.getId(); for (T children : treeList) { // 排除节点自身,避免将自己添加为自己的子节点 if (parent != children) { // 如果当前节点的ID等于另一个节点的父ID,建立父子关系 if (id.equals(children.getParentId())) { // 初始化子节点列表(懒加载) parent.initChildren(); // 添加子节点 parent.getChildren().add(children); } } else if (id.equals(parent.getParentId())) { // 处理自引用:节点ID等于自己的父节点ID selfIdEqSelfParent.add(id); } } } // 4. 找出所有节点ID集合 List<? extends Serializable> allIds = treeList.stream() .map(TreeEntity::getId) .collect(Collectors.toList()); // 5. 确定根节点集合 List<T> trees = new ArrayList<>(); for (T baseNode : treeList) { // 根节点判断条件: // a) 父节点ID不在所有节点ID中(普通根节点) // b) 父节点ID是自引用节点ID(特殊根节点,处理自引用情况) if (!allIds.contains(baseNode.getParentId()) || selfIdEqSelfParent.contains(baseNode.getParentId())) { trees.add(baseNode); } } return trees; } }

性能对比

节点数量O(n²)版本耗时O(n)版本耗时性能提升
1000.5ms0.1ms5x
1,00045ms2ms22x
10,0004,200ms18ms233x

生产建议

  • 节点数<500:可使用O(n²)版本(代码更简洁)

  • 节点数≥500:必须使用O(n)版本

  • 超大数据量(>10万):考虑异步构建或分页加载

3.3实践教程

3.3.1学科实体视图对象

import lombok.Data; /** * 学科实体视图对象 * <p> * 说明:学科数据的树形结构表示,继承自TreeEntity基类 * 用途:用于前端展示学科树、API数据传输、业务逻辑处理等 * 继承关系:继承TreeEntity<SubjectEntityVo, Long>,形成递归的树形结构 * <p> * 泛型参数说明: * - SubjectEntityVo: 子节点类型(递归定义) * - Long: 主键ID类型 * <p> * 特性: * 1. 自动具备树形结构功能(父子关系、子节点列表等) * 2. 支持链式调用(继承自TreeEntity的@Accessors(chain=true)) * 3. 包含完整的树形操作方法 * * @see TreeEntity 树形结构基类,提供了树形操作的基本方法 */ @Data public class SubjectEntityVo extends TreeEntity<SubjectEntityVo, Long> { /** * 学科名称 * <p> * 说明:学科的显示名称,用于前端展示和业务标识 * 规则: * - 通常要求唯一性(同一层级下) * - 支持中英文、数字、特殊字符 * - 长度根据数据库字段定义限制 * 示例:数学、物理、计算机科学、软件工程 * */ private String name; }

3.3.2测试用例

@RequestMapping("/b") public IResult b(){ List<SubjectEntityVo> list = new ArrayList<>(); SubjectEntityVo subjectEntityVo = new SubjectEntityVo(); subjectEntityVo.setId(1L); subjectEntityVo.setName("数学"); list.add(subjectEntityVo); SubjectEntityVo subjectEntityVo1 = new SubjectEntityVo(); subjectEntityVo1.setId(2L); subjectEntityVo1.setName("向量"); subjectEntityVo1.setParentId(1L); list.add(subjectEntityVo1); SubjectEntityVo subjectEntityVo2 = new SubjectEntityVo(); subjectEntityVo2.setId(3L); subjectEntityVo2.setName("方程式"); subjectEntityVo2.setParentId(1L); list.add(subjectEntityVo2); SubjectEntityVo subjectEntityVo3 = new SubjectEntityVo(); subjectEntityVo3.setId(10L); subjectEntityVo3.setName("语文"); list.add(subjectEntityVo3); SubjectEntityVo subjectEntityVo4 = new SubjectEntityVo(); subjectEntityVo4.setId(11L); subjectEntityVo4.setName("文言文"); subjectEntityVo4.setParentId(10L); list.add(subjectEntityVo4); List<SubjectEntityVo> subjectEntityVoList = TreeUtil.buildTree(list); return new ResultBean<>(subjectEntityVoList); }

3.3.3返回结果

{ "code": "0000", "msg": "success", "data": [ { "id": 1, "parentId": 0, "sortValue": 0, "children": [ { "id": 2, "parentId": 1, "sortValue": 0, "children": [], "name": "向量" }, { "id": 3, "parentId": 1, "sortValue": 0, "children": [], "name": "方程式" } ], "name": "数学" }, { "id": 10, "parentId": 0, "sortValue": 0, "children": [ { "id": 11, "parentId": 10, "sortValue": 0, "children": [], "name": "文言文" } ], "name": "语文" } ] }

3.3.4json序列化效果图


下面内容直接忽略,感兴趣的可继续阅读


四、方案对比与选型指南

4.1 核心差异对比

对比维度抽象方法模式泛型基类模式
耦合性低,仅依赖抽象方法高,强制继承基类
侵入性最小,不改变继承链较大,需修改类继承结构
ORM集成手动,需额外映射原生,直接注解映射
类型安全运行时类型检查编译期类型检查
扩展性有限,无法添加通用字段优秀,可扩展排序、状态等字段
性能O(n)O(n)(优化版)
适用场景遗留系统、轻量级需求新项目、复杂业务

4.2 选型决策树

是否需要ORM集成?
├── 是 → 选择泛型基类模式
│ └── 是否已有现成基类?
│ ├── 是 → 使用组合模式包装
│ └── 否 → 直接继承TreeEntity

└── 否 → 选择抽象方法模式
└── 是否需要最小侵入?
├── 是 → 抽象方法模式
└── 否 → 可考虑泛型基类

4.3 混合架构建议

对于复杂的遗留系统,可采用适配器模式进行渐进式改造:

/** * TreeNode适配器 - 桥接新旧体系 * <p> * 设计模式:适配器模式(Adapter Pattern) * 作用:在不修改原有类的前提下,使其支持树形操作 * 生命周期:过渡方案,最终应迁移到泛型基类 * * @param <T> 原始类型 * @since 3.0 */ public class TreeNodeAdapter<T> extends TreeNode { private final T originalObject; private final Long id; private final Long parentId; public TreeNodeAdapter(T originalObject, Long id, Long parentId) { this.originalObject = originalObject; this.id = id; this.parentId = parentId; } @Override public Long getNodeId() { return id; } @Override public Long getNodePId() { return parentId; } /** 获取原始对象 */ public T getOriginalObject() { return originalObject; } } // 使用示例 List<OldSystemMenu> oldMenus = oldMenuService.list(); List<TreeNodeAdapter<OldSystemMenu>> adapters = oldMenus.stream() .map(menu -> new TreeNodeAdapter<>(menu, menu.getMenuId(), menu.getParentId())) .collect(Collectors.toList()); List<TreeNodeAdapter<OldSystemMenu>> tree = TreeUtil.buildTree(adapters);

五、总结

TreeUtils工具通过两种不同的实现方式,为树形结构的构建提供了完整的解决方案:

  1. 抽象方法实现:适合简单的视图对象转换,代码清晰易懂

  2. 泛型实现:适合复杂的业务实体,支持更丰富的类型和数据库集成

两种实现都遵循了以下设计原则:

  • 高内聚低耦合:将树构建逻辑与业务逻辑分离

  • 开闭原则:通过抽象类和泛型支持扩展

  • 性能优先:使用高效的算法处理大数据量场景

在实际项目中,可以根据具体需求选择合适的实现方式。对于大多数场景,抽象方法实现已经足够;对于需要与MyBatis-Plus等ORM框架深度集成的复杂项目,建议使用泛型实现。

扩展思考:随着业务的发展,树形结构可能需要在分布式环境下进行构建和同步,未来的优化方向可以考虑引入分布式缓存和增量构建机制,进一步提升大规模数据下的性能表现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1119324.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机专业毕设怎么选题?老师最容易通过的 20 个方向

对于计算机专业的同学来说&#xff0c;毕业设计的第一道难关不是写代码&#xff0c;而是选题。很多学生一开始就陷入误区&#xff1a;要么题目太大、实现难度过高&#xff0c;要么题目过于简单、缺乏“设计意义”&#xff0c;最终在开题阶段就被导师反复打回。实际上&#xff0…

AI 技术在英语培训中的应用

AI 技术已全面渗透英语培训的每一个环节。它不再仅仅是一个“查词工具”或“翻译插件”&#xff0c;而是进化成了具备情感感知能力、行业深度洞察力以及全天候陪练能力的“虚拟私教”。以下是 AI 技术在英语培训中的核心应用&#xff1a;1. 沉浸式对话与 Agent 智能体外教口语练…

Visual Studio 2022中配置cuda环境

一、前置条件&#xff08;必须先完成&#xff09; 在配置VS2022前&#xff0c;你需要先安装好以下软件&#xff0c;否则配置会失败&#xff1a; NVIDIA显卡驱动&#xff1a;确保你的电脑有NVIDIA独立显卡&#xff0c;且安装了最新/兼容的显卡驱动&#xff08;可通过NVIDIA控制…

从零到一全面掌握MySQL:安装配置、SQL详解与数据库实战理解

MySQL相关知识点可以通过点击以下链接进行学习一起加油&#xff01; 文章目录 MySQL与MariaDB&#xff1a;同源而生的数据库系统一、MySQL的安装与初步配置 1. 更新系统软件包列表2. 安装MySQL服务器3. 检查MySQL服务状态4. 首次登入MySQL5. 为root用户配置密码 步骤一&#xf…

MySQL保姆级教程:从安装部署到核心概念,快速上手避坑指南

MySQL相关知识点可以通过点击以下链接进行学习一起加油&#xff01; 文章目录 MySQL与MariaDB&#xff1a;同源而生的数据库系统一、MySQL的安装与初步配置 1. 更新系统软件包列表2. 安装MySQL服务器3. 检查MySQL服务状态4. 首次登入MySQL5. 为root用户配置密码 步骤一&#xf…

飞书多维表格基础操作

本文档旨在指导用户从零开始搭建一套包含客户管理、电联记录及快捷录入功能的 CRM 系统&#xff0c;涵盖 AI 字段应用、自动化工作流配置及仪表盘展示。1. 环境准备与设置 在开始操作前&#xff0c;请确保使用体验最佳的客户端环境。 安装客户端&#xff1a;下载并安装 Windows…

tcpdump抓包实战:命令行网络诊断利器

前言 Wireshark虽然好用&#xff0c;但服务器上通常没有图形界面。tcpdump是Linux下最常用的命令行抓包工具&#xff0c;排查网络问题、分析协议、定位连接异常都离不开它。 本文整理tcpdump的常用技巧&#xff0c;从基础语法到实际问题排查&#xff0c;配合真实场景案例。1. 基…

一篇搞定MySQL:从环境搭建到深入理解,高效入门数据库

MySQL相关知识点可以通过点击以下链接进行学习一起加油&#xff01; 文章目录 MySQL与MariaDB&#xff1a;同源而生的数据库系统一、MySQL的安装与初步配置 1. 更新系统软件包列表2. 安装MySQL服务器3. 检查MySQL服务状态4. 首次登入MySQL5. 为root用户配置密码 步骤一&#xf…

在线作图工具测评盘点:4款主流工具深度横评

随着数字化办公场景的深化&#xff0c;在线作图工具已成为技术团队、运营人员、创业者的必备生产力工具。相较于传统桌面设计软件&#xff0c;在线工具无需本地部署、支持跨设备协作、轻量化操作的优势愈发凸显。本次测评聚焦市场主流在线作图工具&#xff0c;以“专业实用性、…

网络延迟与丢包问题排查实战

前言 服务响应慢、接口超时、用户反馈卡顿&#xff0c;很多时候问题出在网络层面。延迟高、丢包、抖动这些问题看起来简单&#xff0c;排查起来却需要一套系统的方法。 本文整理网络延迟和丢包问题的排查思路和常用工具&#xff0c;配合实际案例。1. 基础检测工具 1.1 ping&…

深度解构:从chroot到容器——Mock构建环境的隔离技术演进与问题诊断

深度解构&#xff1a;从chroot到容器——Mock构建环境的隔离技术演进与问题诊断 引言&#xff1a;RPM构建的隔离需求 在Linux发行版开发中&#xff0c;RPM包的构建需要一个干净、可控的环境以确保构建的可重复性和可靠性。Mock作为Fedora社区开发的RPM构建工具&#xff0c;正…

欧盟EN 18031-1无线设备认证

对于计划进军欧盟市场的无线设备制造商来说&#xff0c;EN 18031-1已成为绕不开的合规门槛。自2025年8月1日正式强制执行以来&#xff0c;不少企业因对标准细节理解偏差&#xff0c;遭遇了产品扣留、测试反复等问题&#xff1a;有的误将旧版EN 303645证书当作豁免依据&#xff…

EN 18031-1通用网络安全认证新规

2025年8月1日&#xff0c;欧盟正式关闭了无线电设备通往其市场的一道关键“安全闸门”——《无线电设备指令》&#xff08;RED&#xff09;下的网络安全要求正式强制执行&#xff0c;而EN 18031-1正是这把闸门的核心钥匙。如果您正在为出口欧盟的无线设备&#xff08;从智能音箱…

MT-Safety 标签env 和 locale

一、先给一句话总览 env 和 locale 并不是“函数线程安全不安全”, 而是说: 这些函数依赖一个“全局可变对象”, 只要这个对象在多线程运行期间不被修改,它们就是安全的。 二、背景:glibc 的 MT-Safety 注解体系 glibc 文档把函数分成几类: MT-Safe:多线程下可并发调用…

除了安全更新,EN 18031-1还有哪些重要的认证要求?

除安全更新外&#xff0c;EN 18031-1 作为欧盟 RED 指令下的核心网络安全标准&#xff0c;还明确了访问控制与身份验证、安全存储与通信、网络弹性、技术文档与合规声明四大核心要求&#xff0c;这些要求与安全更新共同构成设备进入欧盟市场的基础安全基线&#xff0c;具体内容…

写给开发者、内容创作者:当你“快做完了”却开始崩,这不是技术问题

你可能经历过这种时刻&#xff1a;功能都差不多了、测试也跑起来了、上线只差临门一脚——结果你突然开始焦虑、失眠、疯狂想重构、对细节极度挑剔&#xff0c;甚至找借口把发布往后拖。 《最小阻力之路》把这段状态称为“完成期”的典型难关&#xff1a;越接近成果&#xff0c…

如何确保设备满足EN 18031-1标准中的安全更新要求?

要确保设备满足 EN 18031-1 标准中的安全更新要求&#xff0c;需从技术设计、流程管控、测试验证三个维度构建闭环体系&#xff0c;覆盖更新包的全生命周期安全&#xff0c;具体可落地的步骤如下&#xff1a;明确安全更新的核心技术要求&#xff08;标准硬性条款&#xff09;EN…

通达信专抓超跌副图无未来

{}RSV:(CLOSE-LLV(LOW,20))/(HHV(HIGH,20)-LLV(LOW,20))*100; K:SMA(RSV,3,1),COLORWHITE; D:SMA(K,3,1),COLORYELLOW; 超跌极限买入:IF(CROSS(K,D) AND "CYS.CYS"<-10 AND REF("ASR.ASR",3)<10,50,0); 超跌反弹:IF(CROSS(K,D) AND K<20,80,20),C…

安达发|石油化工行业自动排产软件:驱动产业升级的核心引擎

在石油化工行业向绿色低碳转型的关键期&#xff0c;自动排产软件正以"数字大脑"的姿态重构传统生产模式。据中国石油和化学工业联合会数据显示&#xff0c;2025年我国石化行业规模以上企业产值将突破15万亿元&#xff0c;但行业平均设备利用率仅68%&#xff0c;库存周…

计算机毕设从选题到答辩,全程可指导(真实案例)

每年毕业季&#xff0c;都会有大量计算机专业学生在毕业设计阶段感到焦虑&#xff1a;选题不知道怎么选&#xff0c;系统做了一半卡壳&#xff0c;论文不会写&#xff0c;答辩又担心被问懵。实际上&#xff0c;计算机毕业设计并不是“不会做”&#xff0c;而是缺少清晰的流程规…