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索引

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

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

相关推荐
梅孔立42 分钟前
OpenCode + Windows PowerShell 完整安装使用教程
windows
2601_962364971 小时前
面向设备厂商:Windows Hello 认证摄像头核心要点
windows·安全·电脑
云上先途1 小时前
属性标注是什么?主要解决什么问题?
大数据·人工智能
大大大大晴天4 小时前
每天认识一个组件:元数据平台OpenMetadata
大数据
接口不宕机4 小时前
1688 商品列表 API 对接商家 ERP,实现店铺商品同步与上下架自动监控
大数据
河南花仙子科技6 小时前
企业定制小游戏助力品牌软性传播
大数据·科技·游戏·小程序
云上先途6 小时前
标签化服务适合哪些人?常见适用场景一次讲清
大数据·人工智能·算法·音视频
yt004yt6 小时前
### 越华环保集团|绿岛 VOCs 集中治理,管网系统安全设计工程实战分享
大数据·运维
GreatVicent7 小时前
腾讯AI产业大会解读:Agent进场,企业基础设施怎么搭
大数据·数据库·人工智能·llm·agent·企业信息化
m0_466525297 小时前
从火柴人看护画面看云从科技生态企业的隐私保护实践
大数据·人工智能·科技·microsoft