news 2026/6/21 13:44:38

手把手教你封装一个树形结构处理类(Java 通用 Tree 工具,支持无限层级)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手把手教你封装一个树形结构处理类(Java 通用 Tree 工具,支持无限层级)

视频看了几百小时还迷糊?关注我,几分钟让你秒懂!


🧩 一、需求场景:为什么需要树形结构?

在实际开发中,树形结构无处不在:

  • 组织架构:公司 → 部门 → 小组 → 员工;
  • 菜单权限:系统管理 → 用户管理 → 新增/编辑;
  • 商品分类:电子产品 → 手机 → 智能手机 → 国产;
  • 评论回复:主评论 → 子评论 → 孙评论。

但数据库通常用“平铺表”存储(id,parent_id,name),如何高效转成树?

✅ 目标:一行代码,将 List 转为 Tree!


🛠️ 二、通用树节点接口设计

先定义一个通用接口,让所有树节点实现它:

import java.util.List; public interface TreeNode<T> { String getId(); String getParentId(); void setChildren(List<T> children); }

💡 使用泛型T,支持任意实体类(Menu、Dept、Category 等)。


📦 三、示例实体类(以菜单为例)

import lombok.Data; import java.util.ArrayList; import java.util.List; @Data public class Menu implements TreeNode<Menu> { private String id; private String parentId; private String name; private Integer sort; private List<Menu> children = new ArrayList<>(); @Override public String getId() { return this.id; } @Override public String getParentId() { return this.parentId; } @Override public void setChildren(List<Menu> children) { this.children = children; } }

🔔 注意:children字段必须存在,并提供 setter。


🌲 四、核心工具类:TreeBuilder(重点!)

import java.util.*; import java.util.stream.Collectors; public class TreeBuilder { /** * 将平铺列表构建成树形结构 * * @param nodes 所有节点(平铺) * @param rootParentId 根节点的 parentId(如 "0" 或 null) * @param <T> 节点类型 * @return 树形列表 */ public static <T extends TreeNode<T>> List<T> buildTree(List<T> nodes, String rootParentId) { if (nodes == null || nodes.isEmpty()) { return Collections.emptyList(); } // 1. 创建 id -> node 的映射,提升查找效率 O(1) Map<String, T> nodeMap = nodes.stream() .collect(Collectors.toMap(TreeNode::getId, node -> node)); // 2. 初始化 children 列表 Map<String, List<T>> childrenMap = new HashMap<>(); for (T node : nodes) { childrenMap.put(node.getId(), new ArrayList<>()); } // 3. 遍历所有节点,构建父子关系 List<T> roots = new ArrayList<>(); for (T node : nodes) { String parentId = node.getParentId(); if (Objects.equals(parentId, rootParentId)) { // 是根节点 roots.add(node); } else { // 找到父节点,添加到其 children T parent = nodeMap.get(parentId); if (parent != null) { childrenMap.get(parentId).add(node); } // 如果父节点不存在,可选择忽略或抛异常 } } // 4. 设置每个节点的 children for (Map.Entry<String, List<T>> entry : childrenMap.entrySet()) { String nodeId = entry.getKey(); T node = nodeMap.get(nodeId); if (node != null) { node.setChildren(entry.getValue()); } } return roots; } }

✅ 时间复杂度:O(n),远优于递归(O(n²))!


🧪 五、使用示例

1. 模拟数据库数据

public class TreeDemo { public static void main(String[] args) { List<Menu> menus = Arrays.asList( new Menu("1", "0", "系统管理", 1), new Menu("2", "0", "内容管理", 2), new Menu("3", "1", "用户管理", 1), new Menu("4", "1", "角色管理", 2), new Menu("5", "3", "新增用户", 1), new Menu("6", "3", "编辑用户", 2), new Menu("7", "2", "文章管理", 1) ); // 构建树 List<Menu> tree = TreeBuilder.buildTree(menus, "0"); // 打印结果(可用 Jackson 格式化) System.out.println(tree); } }

2. 输出效果(结构化)

[ { "id": "1", "parentId": "0", "name": "系统管理", "children": [ { "id": "3", "parentId": "1", "name": "用户管理", "children": [ {"id": "5", "parentId": "3", "name": "新增用户", "children": []}, {"id": "6", "parentId": "3", "name": "编辑用户", "children": []} ] }, { "id": "4", "parentId": "1", "name": "角色管理", "children": [] } ] }, { "id": "2", "parentId": "0", "name": "内容管理", "children": [ {"id": "7", "parentId": "2", "name": "文章管理", "children": []} ] } ]

✅ 完美支持无限层级


❌ 六、反例 & 常见错误

反例 1:用递归构建树(性能差)

// ❌ 每找一个子节点都要遍历整个 list,n 层树 → O(n²) public List<Menu> buildByRecursion(String parentId, List<Menu> all) { return all.stream() .filter(m -> Objects.equals(m.getParentId(), parentId)) .peek(m -> m.setChildren(buildByRecursion(m.getId(), all))) .collect(Collectors.toList()); }

💥 数据量大时(如 1000+ 节点),响应慢到超时!


反例 2:不处理“父节点不存在”的情况

  • 数据库里有个节点parentId = "999",但id=999的记录被删了;
  • 如果不处理,可能 NPE 或数据丢失。

✅ 建议:

  • 日志告警:“孤立节点:id=xxx”;
  • 或自动挂到根节点下(根据业务决定)。

反例 3:硬编码实体类,无法复用

// ❌ 只能处理 Menu,不能处理 Dept、Category public class MenuTreeBuilder { ... }

✅ 正确:用泛型 + 接口,一套代码通吃所有树!


⚠️ 七、增强建议(生产级)

需求实现方式
排序buildTree后对children调用sort()
过滤支持传入Predicate<T>过滤节点
路径递归生成path = /系统管理/用户管理
扁平化提供flattenTree()方法反向操作
循环引用检测构建时检查id == parentId或环形依赖

例如,加排序:

// 在 buildTree 最后加: roots.forEach(TreeBuilder::sortChildren); private static <T extends TreeNode<T>> void sortChildren(T node) { if (node instanceof Comparable) { node.getChildren().sort(null); } node.getChildren().forEach(TreeBuilder::sortChildren); }

🎯 八、总结

特性说明
通用性任何实现TreeNode的类都能用
高性能O(n) 时间复杂度,Map 索引加速
安全处理孤立节点、空值等边界情况
易用一行代码TreeBuilder.buildTree(list, "0")

从此告别手写递归,轻松搞定组织架构、菜单、分类树!


视频看了几百小时还迷糊?关注我,几分钟让你秒懂!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/13 14:59:39

枚举类 enum class:强类型枚举的优势

枚举类 enum class&#xff1a;强类型枚举的优势 在C编程中&#xff0c;枚举类型是用于表示离散常量集合的基础工具&#xff0c;传统枚举&#xff08;enum&#xff09;虽能简化常量定义&#xff0c;但存在类型模糊、作用域污染、隐式转换等缺陷&#xff0c;在复杂项目中易引发…

作者头像 李华
网站建设 2026/6/19 12:34:00

局域网内WebUploader怎样支持大文件分段与断点续传?

前端程序员外包项目救星&#xff1a;原生JS大文件上传组件&#xff08;Vue3实现&#xff09; 兄弟&#xff0c;作为在杭州接外包的老前端程序员&#xff0c;太懂你现在的处境了——甲方要20G大文件上传&#xff0c;还要兼容IE9&#xff0c;预算卡得死死的&#xff0c;网上代码…

作者头像 李华
网站建设 2026/6/15 15:40:13

国产化系统中WebUploader如何处理局域网大文件断点续传?

要求&#xff1a;免费,开源,技术支持 技术&#xff1a;百度webuploader&#xff0c;分块&#xff0c;切片&#xff0c;断点续传&#xff0c;秒传&#xff0c;MD5验证&#xff0c;纯JS实现&#xff0c;支持第三方软件集成 前端&#xff1a;vue2,vue3,vue-cli,html5,webuploader …

作者头像 李华
网站建设 2026/6/20 10:30:29

国防项目中使用KindEditor如何加密转存WORD涉密图片?

企业网站后台管理系统增强功能需求分析与解决方案 一、需求分析 作为北京集团上市公司项目负责人&#xff0c;我负责评估和实施在企业网站后台管理系统文章发布模块中增加以下功能&#xff1a; Word粘贴功能&#xff1a;支持从Word复制内容粘贴到网站编辑器&#xff0c;图片…

作者头像 李华
网站建设 2026/6/17 4:30:07

msvcp140.dll文件丢失在系统 打不开程序 免费下载方法分享

在使用电脑系统时经常会出现丢失找不到某些文件的情况&#xff0c;由于很多常用软件都是采用 Microsoft Visual Studio 编写的&#xff0c;所以这类软件的运行需要依赖微软Visual C运行库&#xff0c;比如像 QQ、迅雷、Adobe 软件等等&#xff0c;如果没有安装VC运行库或者安装…

作者头像 李华
网站建设 2026/6/20 19:00:57

互联网产品使用KindEditor如何生成WORD图片URL链接?

震惊&#xff01;PHP程序员遭遇"680元预算做Office全家桶"的奇幻漂流 大家好&#xff0c;我是北京某PHP码农老张&#xff08;头发比工资少的那种&#xff09;。最近接了个CMS项目&#xff0c;客户要求把Word、Excel、PPT、PDF甚至微信公众号内容统统塞进编辑器&…

作者头像 李华