Hutool TreeUtil 教程

一、简介

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());
        });
}

九、注意事项

  1. 数据类型统一: id和parentId建议使用相同数据类型(如String或Long)

  2. 根节点标识: 确保根节点的parentId匹配指定的根ID值

  3. 性能考虑: 大数据量(>10000)时注意性能,TreeUtil内部使用Map索引,性能良好

  4. 线程安全: TreeUtil本身是线程安全的,但结果需要自行处理

  5. 空值处理: 如果parentId为null,会被当作根节点处理

十、总结

Hutool TreeUtil 的优势:

  • ✅ 简单易用,代码简洁

  • ✅ 性能优秀,使用Map索引

  • ✅ 配置灵活,支持自定义字段

  • ✅ 功能完善,满足大部分树形结构需求

相关推荐
破阵子443282 小时前
PowerShell-7 下载安装教程(如何更新 PowerShell)
windows
科技AI训练师2 小时前
2026工业风机行业观察:英飞风机在中高端通风排烟领域表现
大数据·人工智能
大大大大晴天️2 小时前
Flink技术实践-Flink指标监控全景指南
大数据·flink
蓝眸少年CY2 小时前
Canal - 数据同步
大数据·canal
中科天工2 小时前
中科天工智能包装技术是什么?
大数据·人工智能
Chuer_2 小时前
AI For BI是什么?一文拆解AI For BI应用落地!
大数据·数据库·人工智能·安全·数据分析·甘特图
人工智能培训2 小时前
是否需要构建包含真实物理噪声的仿真环境?
大数据·人工智能·prompt·agent·智能体
安当加密2 小时前
指纹一按,安全上线:指纹登录如何为生产线 Windows 电脑实现低成本防勒索?
windows·安全·电脑
不做超级小白2 小时前
解密Win11的Win+S搜索:从索引原理到自启动的攻防战
windows·安全