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

手把手教你封装一个树形结构处理类(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")

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


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

http://www.cnnetsun.cn/news/860567.html

相关文章:

  • 【计算机网络】ep1:物理层概述
  • 百度开源上传组件在局域网如何处理大文件断点续传?
  • PostgreSQL MCP
  • 高危!Apache Parquet Java库曝远程代码执行(RCE)漏洞,需立即修复
  • 大模型工具使用指南:MCP与Skills对比分析,收藏级技术解析
  • 从0到1掌握RAG:解决大模型落地痛点的终极方案,建议收藏!
  • Nodejs+vuenet基于位置管理的企业 员工考勤打卡系统设计app小程序
  • 我让AI读了产品PRD,自动生成“验收标准”测试用例
  • 2026年效率翻倍:开发者必装的AI助手APP
  • AI测试用例生成的“异常流”缺失:一场未被教导的盲区
  • ‌AI驱动的测试用例模板统一实践:从标准框架到团队协同的完整路径
  • 基于AI课堂+Spring Boot +Vue的面向中职学校的第二课堂教学管理系统 毕业设计项目实战辅导指导
  • 基于人工智能AI + Spring Boot + AI建议分析建筑工程项目管理系统 毕业设计项目实战辅导指导
  • 为什么工业智能化需要工业AI大脑?应该如何选择?
  • 别再“假装数字化”了!3分钟搞懂:什么叫“数字化成效”,以及怎么用最少的钱干最靓的事!
  • Nodejs+vue新闻订阅推荐系统头条app的设计与实现 小程序
  • 看懂风扇的“里外”:原理、构造、性能与计算的系统性解读
  • 10个技巧:用AI自动生成测试报告
  • 【值得珍藏】LLM推理优化技术详解:从数据级到系统级的全面解析
  • 9999999
  • AI创作避坑 学术党实测有效,免费搞定查重+绘图+改稿
  • 收藏必看!告别RAG碎片化:一文讲透Forms-Dynamics框架下的Agent记忆系统
  • 收藏!AI大模型应用开发工程师全解析,小白程序员必看的入行指南
  • Pelco KBD300A 模拟器:17.按照pytest自动化测试方案规划建立测试基础框架
  • 听说现在JDBC已经过时了,还需要学吗?
  • 开题报告被批 “无逻辑、缺创新”?虎贲等考 AI 一键解锁 “过审密码”
  • Nodejs+vueAndroid的垃圾分类系统小程序
  • 我的区块链运维日记 · 第 12 日:消失的服务器 —— 也就是我们如何被 IPFS 逼疯的
  • nginx和openresty和apisix区别
  • 用 Claude Code 重新定义编程效率:一次真实开发实践