一、简介
Hutool 的 TreeUtil 是一个树结构工具类,用于处理节点数据构建树形结构(如菜单树、组织树、地区树等),支持无限级分类。
二、核心概念
2.1 关键类
-
TreeNode: 树节点配置类,定义节点属性(id、parentId、children等)
-
TreeUtil: 工具类,提供构建树的方法
三、快速开始
3.1 添加依赖
XML
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.26</version>
</dependency>
3.2 定义节点类
java
import lombok.Data;
import java.util.List;
@Data
public class Menu {
private String id;
private String parentId;
private String name;
private Integer sort;
private List<Menu> children;
}
四、基本用法
4.1 方式一:使用 TreeNodeConfig
java
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
public class TreeUtilDemo {
public static void main(String[] args) {
// 构建节点列表
List<Menu> menus = buildMenuList();
// 配置树节点
TreeNodeConfig config = new TreeNodeConfig();
config.setIdKey("id"); // 节点ID字段名
config.setParentIdKey("parentId"); // 父节点ID字段名
config.setWeightKey("sort"); // 排序字段
config.setChildrenKey("children"); // 子节点字段名
config.setDeep(3); // 最大深度
// 构建树
List<Tree<String>> treeList = TreeUtil.build(menus, "0", config,
(menu, tree) -> {
tree.setId(menu.getId());
tree.setParentId(menu.getParentId());
tree.setName(menu.getName());
tree.setWeight(menu.getSort());
// 添加额外属性
tree.putExtra("extra", "自定义值");
});
System.out.println(JSONUtil.toJsonPrettyStr(treeList));
}
}
4.2 方式二:简化版
java
// 如果节点类实现了 TreeNode 接口
List<Tree<String>> treeList = TreeUtil.build(menus, "0");
五、完整示例
5.1 构建菜单树
java
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.json.JSONUtil;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class Menu {
private String id;
private String parentId;
private String name;
private Integer sort;
private String path;
}
public class MenuTreeBuilder {
public static List<Tree<String>> buildMenuTree(List<Menu> menus) {
// 配置
TreeNodeConfig config = new TreeNodeConfig();
config.setIdKey("id");
config.setParentIdKey("parentId");
config.setWeightKey("sort");
// 构建树
return TreeUtil.build(menus, "0", config,
(menu, tree) -> {
tree.setId(menu.getId());
tree.setParentId(menu.getParentId());
tree.setName(menu.getName());
tree.setWeight(menu.getSort());
// 自定义字段
tree.putExtra("path", menu.getPath());
tree.putExtra("type", "menu");
});
}
public static void main(String[] args) {
List<Menu> menus = new ArrayList<>();
// 一级菜单
Menu menu1 = new Menu();
menu1.setId("1");
menu1.setParentId("0");
menu1.setName("系统管理");
menu1.setSort(1);
menu1.setPath("/system");
// 二级菜单
Menu menu11 = new Menu();
menu11.setId("11");
menu11.setParentId("1");
menu11.setName("用户管理");
menu11.setSort(1);
menu11.setPath("/system/user");
Menu menu12 = new Menu();
menu12.setId("12");
menu12.setParentId("1");
menu12.setName("角色管理");
menu12.setSort(2);
menu12.setPath("/system/role");
menus.add(menu1);
menus.add(menu11);
menus.add(menu12);
List<Tree<String>> tree = buildMenuTree(menus);
System.out.println(JSONUtil.toJsonPrettyStr(tree));
}
}
5.2 输出结果
java
[
{
"id": "1",
"parentId": "0",
"name": "系统管理",
"weight": 1,
"path": "/system",
"type": "menu",
"children": [
{
"id": "11",
"parentId": "1",
"name": "用户管理",
"weight": 1,
"path": "/system/user",
"type": "menu"
},
{
"id": "12",
"parentId": "1",
"name": "角色管理",
"weight": 2,
"path": "/system/role",
"type": "menu"
}
]
}
]
六、高级用法
6.1 自定义根节点ID
java
// 使用不同的根节点标识
List<Tree<String>> tree = TreeUtil.build(nodes, "-1", config, (node, tree) -> {
// 映射逻辑
});
6.2 带条件的树构建
java
// 过滤无效节点
List<Menu> validMenus = menus.stream()
.filter(m -> m.getStatus() == 1) // 只构建启用的菜单
.collect(Collectors.toList());
List<Tree<String>> tree = TreeUtil.build(validMenus, "0", config, ...);
6.3 多根节点树
java
// 自动识别多个根节点(parentId 不在列表中的节点)
List<Tree<String>> tree = TreeUtil.build(menus, null, config, ...);
6.4 获取树中所有节点ID
java
import cn.hutool.core.lang.tree.Tree;
import java.util.ArrayList;
import java.util.List;
public List<String> getAllNodeIds(List<Tree<String>> trees) {
List<String> ids = new ArrayList<>();
for (Tree<String> tree : trees) {
ids.add(tree.getId());
if (tree.getChildren() != null) {
ids.addAll(getAllNodeIds(tree.getChildren()));
}
}
return ids;
}
七、常见问题
7.1 节点顺序问题
java
// 设置权重字段进行排序
config.setWeightKey("sort");
// 或者使用 Comparator
config.setComparator(Comparator.comparing(Tree::getWeight));
7.2 循环引用问题
确保数据本身没有循环引用(如A的父ID是B,B的父ID是A),TreeUtil会处理这种情况,但可能导致栈溢出。
7.3 性能优化
java
// 大量数据时,使用并行流处理
List<Tree<String>> tree = TreeUtil.build(menus, "0", config, (menu, tree) -> {
// 映射逻辑
});
// TreeUtil 内部已优化,无需额外处理
八、实际应用场景
8.1 地区选择器
java
public List<Tree<String>> buildRegionTree(List<Region> regions) {
TreeNodeConfig config = new TreeNodeConfig();
config.setIdKey("code");
config.setParentIdKey("parentCode");
config.setWeightKey("orderNum");
return TreeUtil.build(regions, "0", config,
(region, tree) -> {
tree.setId(region.getCode());
tree.setParentId(region.getParentCode());
tree.setName(region.getName());
tree.putExtra("level", region.getLevel());
tree.putExtra("zipCode", region.getZipCode());
});
}
8.2 部门树
java
public List<Tree<String>> buildDeptTree(List<Department> depts) {
TreeNodeConfig config = new TreeNodeConfig();
config.setIdKey("deptId");
config.setParentIdKey("parentId");
config.setWeightKey("orderNum");
config.setDeep(5);
return TreeUtil.build(depts, "0", config,
(dept, tree) -> {
tree.setId(dept.getDeptId());
tree.setParentId(dept.getParentId());
tree.setName(dept.getDeptName());
tree.putExtra("leader", dept.getLeader());
tree.putExtra("phone", dept.getPhone());
tree.putExtra("status", dept.getStatus());
});
}
九、注意事项
-
数据类型统一: id和parentId建议使用相同数据类型(如String或Long)
-
根节点标识: 确保根节点的parentId匹配指定的根ID值
-
性能考虑: 大数据量(>10000)时注意性能,TreeUtil内部使用Map索引,性能良好
-
线程安全: TreeUtil本身是线程安全的,但结果需要自行处理
-
空值处理: 如果parentId为null,会被当作根节点处理
十、总结
Hutool TreeUtil 的优势:
-
✅ 简单易用,代码简洁
-
✅ 性能优秀,使用Map索引
-
✅ 配置灵活,支持自定义字段
-
✅ 功能完善,满足大部分树形结构需求