当前位置: 首页 > 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.jsqmd.com/news/316955/

相关文章:

  • 一文带你掌握 Nginx 配置:从零搭建高性能 Web 服务(附实战案例)
  • 手把手教你设计一个提供给三方调用的接口鉴权(含完整 Java + Spring Boot 实现)
  • 手把手教你封装一个调用三方接口的 HTTP 工具类(Spring Boot + Java 实战)
  • 【计算机网络】ep1:物理层概述
  • Spring Boot 中如何使用自定义配置管理器?告别硬编码,优雅管理你的配置!
  • Nacos 工作原理你真的了解吗?从配置拉取到动态刷新,一文讲透!
  • 【计算机网络】ep0:计算机网络概述
  • Nacos 你真的了解吗?Spring Boot 集成配置中心实战指南(小白也能看懂!)
  • 枚举类 enum class:强类型枚举的优势
  • CTF之——密码破解工具hashcat,零基础入门到精通,看完这篇就足够了~
  • 国内AI编程IDE对比(二):从零构建桌面应用实测
  • Java类型转换
  • 对目前C++方向的一些想法
  • 基础架构即代码?不,Sealos 让基础架构变成了开箱即用
  • 我终于不用在周末处理集群故障了,感谢 Sealos 的架构设计
  • 【26美赛B题】2026美赛数学建模(MCM/ICM)思路解析及代码分享
  • 【26美赛C题】2026美赛数学建模(MCM/ICM)思路解析及代码分享
  • msvcr80d.dll文件丢失找不到问题 免费下载方法分享
  • 国产化系统中WebUploader如何处理局域网大文件断点续传?
  • 百度开源上传组件在局域网如何处理大文件断点续传?
  • 局域网内WebUploader怎样支持大文件分段与断点续传?
  • 浙江万全扑克有限公司 联系方式:背景与联系信息参考
  • 2025环境试验设备厂商大比拼,口碑出炉,盐水喷雾试验箱及各种老化房,环境试验设备生产厂家排行榜
  • 聊聊杭州比较不错的职业装定制专业公司,哪家性价比高看这里
  • 聊聊煜形象个人西服定制职业装定制靠不靠谱,费用多少
  • 聊聊印刷胶辊定制厂家,泰兴金茂辊业区口碑如何
  • 浙江万全扑克有限公司 联系方式:产品选购与使用通用指南
  • 驰创轴承性价比怎么样,看它口碑与核心竞争力表现
  • 平面设计公司价格怎么算,全速网络收费标准是啥?
  • 浙江万全扑克有限公司 联系方式:官方信息查询与核实指引