我来为你提供几种Java实现菜单树形结构的方案:
方案一:使用递归构建树形结构
// 菜单实体类 @Data public class Menu { private String code; private String parentCode; private String name; private Integer sort; private List
// 构造方法、getter、setter省略
}
// 树形结构构建工具类 @Component public class MenuTreeBuilder {
/*** 构建菜单树*/ public List<Menu> buildTree(List<Menu> menuList) {if (menuList == null || menuList.isEmpty()) {return new ArrayList<>();}// 找出所有根节点(parentCode为null或空字符串)List<Menu> rootMenus = menuList.stream().filter(menu -> menu.getParentCode() == null || menu.getParentCode().isEmpty()).sorted(Comparator.comparing(Menu::getSort)).collect(Collectors.toList());// 为每个根节点递归设置子节点rootMenus.forEach(this::setChildren);return rootMenus; }/*** 递归设置子节点*/ private void setChildren(Menu parentMenu) {List<Menu> children = menuList.stream().filter(menu -> parentMenu.getCode().equals(menu.getParentCode())).sorted(Comparator.comparing(Menu::getSort)).collect(Collectors.toList());if (!children.isEmpty()) {parentMenu.setChildren(children);// 递归设置孙子节点children.forEach(this::setChildren);} }
方案二:使用Map优化查询效率
@Service public class MenuService {
/*** 使用Map优化构建树形结构*/ public List<Menu> buildTreeWithMap(List<Menu> menuList) {if (menuList == null || menuList.isEmpty()) {return new ArrayList<>();}// 将列表转为Map,key为code,value为Menu对象Map<String, Menu> menuMap = menuList.stream().collect(Collectors.toMap(Menu::getCode, menu -> menu));List<Menu> tree = new ArrayList<>();for (Menu menu : menuList) {String parentCode = menu.getParentCode();if (parentCode == null || parentCode.isEmpty()) {// 根节点tree.add(menu);} else {// 子节点,找到父节点并添加到children中Menu parent = menuMap.get(parentCode);if (parent != null) {if (parent.getChildren() == null) {parent.setChildren(new ArrayList<>());}parent.getChildren().add(menu);}}}// 对每个层级的节点进行排序sortTree(tree);return tree; }/*** 递归排序树节点*/ private void sortTree(List<Menu> menus) {if (menus == null || menus.isEmpty()) {return;}menus.sort(Comparator.comparing(Menu::getSort));for (Menu menu : menus) {if (menu.getChildren() != null && !menu.getChildren().isEmpty()) {sortTree(menu.getChildren());}} }
方案三:使用Stream API的简洁版本
public class MenuTreeUtils {
/*** 使用Stream构建树形结构*/ public static List<Menu> buildTreeStream(List<Menu> menus) {Map<String, List<Menu>> parentMap = menus.stream().filter(menu -> menu.getParentCode() != null).collect(Collectors.groupingBy(Menu::getParentCode));return menus.stream().filter(menu -> menu.getParentCode() == null || menu.getParentCode().isEmpty()).peek(root -> setChildrenRecursively(root, parentMap)).sorted(Comparator.comparing(Menu::getSort)).collect(Collectors.toList()); }private static void setChildrenRecursively(Menu parent, Map<String, List<Menu>> parentMap) {List<Menu> children = parentMap.getOrDefault(parent.getCode(), new ArrayList<>());if (!children.isEmpty()) {children.sort(Comparator.comparing(Menu::getSort));parent.setChildren(children);children.forEach(child -> setChildrenRecursively(child, parentMap));} }
方案四:完整的Spring Boot使用示例
@RestController @RequestMapping("/api/menus") public class MenuController {
@Autowired private MenuService menuService;@GetMapping("/tree") public ResponseEntity<List<Menu>> getMenuTree() {try {// 模拟从数据库查询菜单数据List<Menu> menuList = getMenuListFromDB();// 构建树形结构List<Menu> menuTree = menuService.buildTreeWithMap(menuList);return ResponseEntity.ok(menuTree);} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();} }private List<Menu> getMenuListFromDB() {// 这里模拟数据库查询结果List<Menu> menus = new ArrayList<>();// 添加测试数据menus.add(createMenu("1", null, "系统管理", 1));menus.add(createMenu("2", "1", "用户管理", 1));menus.add(createMenu("3", "1", "角色管理", 2));menus.add(createMenu("4", "2", "用户列表", 1));menus.add(createMenu("5", "2", "添加用户", 2));menus.add(createMenu("6", null, "业务管理", 2));menus.add(createMenu("7", "6", "订单管理", 1));return menus; }private Menu createMenu(String code, String parentCode, String name, Integer sort) {Menu menu = new Menu();menu.setCode(code);menu.setParentCode(parentCode);menu.setName(name);menu.setSort(sort);return menu; }
方案五:支持多种根节点标识的通用版本
public class AdvancedMenuTreeBuilder {
/*** 构建树形结构 - 支持自定义根节点判断条件*/ public static <T extends TreeNode> List<T> buildTree(List<T> nodes, RootNodePredicate<T> rootPredicate) {if (nodes == null || nodes.isEmpty()) {return new ArrayList<>();}Map<String, T> nodeMap = nodes.stream().collect(Collectors.toMap(TreeNode::getCode, node -> node));List<T> tree = new ArrayList<>();for (T node : nodes) {String parentCode = node.getParentCode();if (rootPredicate.isRoot(node)) {tree.add(node);} else if (parentCode != null && nodeMap.containsKey(parentCode)) {T parent = nodeMap.get(parentCode);if (parent.getChildren() == null) {parent.setChildren(new ArrayList<>());}parent.getChildren().add(node);}}// 排序sortTree(tree);return tree; }private static <T extends TreeNode> void sortTree(List<T> nodes) {nodes.sort(Comparator.comparing(TreeNode::getSort));for (T node : nodes) {if (node.getChildren() != null && !node.getChildren().isEmpty()) {sortTree(node.getChildren());}} }/*** 根节点判断接口*/ @FunctionalInterface public interface RootNodePredicate<T> {boolean isRoot(T node); }
使用示例
// 使用通用版本 List
// 方式1:parentCode为null的是根节点 List
// 方式2:parentCode为0或空字符串的是根节点 List
// 方式3:自定义根节点逻辑 List
性能对比和建议
方案一(递归):代码直观,但大数据量时可能栈溢出
方案二(Map优化):性能最好,推荐使用
方案三(Stream):代码简洁,适合中小数据量
方案五(通用版):最灵活,可复用性强
推荐使用方法二,它在性能和代码可读性之间取得了很好的平衡。
是的,Hutool提供了非常便捷的树形结构构建工具类!主要有以下几种方式:
方案一:使用 TreeUtil 构建树形结构
// 引入依赖 // // cn.hutool // hutool-all // 5.8.16 //
import cn.hutool.core.lang.tree.Tree; import cn.hutool.core.lang.tree.TreeNode; import cn.hutool.core.lang.tree.TreeUtil;
// 菜单实体类实现 TreeNode 接口 @Data public class Menu implements TreeNode { private String id; // 对应 code private String parentId; // 对应 parent_code private String name; private Integer sort; private String weight; // 权重字段,用于排序
@Override public String getId() {return this.id; }@Override public String getName() {return this.name; }@Override public String getParentId() {return this.parentId; }@Override public List<TreeNode<String>> getChildren() {return (List<TreeNode<String>>) (Object) this.children; }@Override public void setChildren(List<TreeNode<String>> children) {this.children = (List<Menu>) children; }@Override public Comparable<?> getWeight() {return this.sort; }private List<Menu> children;
public List<Tree<String>> buildMenuTree() {// 模拟从数据库查询的菜单数据List<Menu> menuList = getMenuListFromDB();// 使用 Hutool TreeUtil 构建树List<Tree<String>> treeList = TreeUtil.build(menuList, "0"); // "0" 表示根节点的 parentIdreturn treeList; }public List<Tree<String>> buildMenuTreeCustom() {List<Menu> menuList = getMenuListFromDB();// 自定义配置构建树List<Tree<String>> treeList = TreeUtil.build(menuList, "0",(treeNode, tree) -> {// 自定义节点信息设置tree.setId(treeNode.getId());tree.setName(treeNode.getName());tree.setParentId(treeNode.getParentId());tree.putExtra("sort", treeNode.getSort()); // 额外属性});return treeList; }private List<Menu> getMenuListFromDB() {List<Menu> menus = new ArrayList<>();menus.add(createMenu("1", "0", "系统管理", 1));menus.add(createMenu("2", "1", "用户管理", 1));menus.add(createMenu("3", "1", "角色管理", 2));menus.add(createMenu("4", "2", "用户列表", 1));menus.add(createMenu("5", "2", "添加用户", 2));menus.add(createMenu("6", "0", "业务管理", 2));return menus; }private Menu createMenu(String id, String parentId, String name, Integer sort) {Menu menu = new Menu();menu.setId(id);menu.setParentId(parentId);menu.setName(name);menu.setSort(sort);return menu; }
方案二:使用 Map 数据直接构建
public class MenuTreeDemo {
public void buildTreeFromMap() {// 直接使用 Map 数据构建,无需创建实体类List<Map<String, Object>> menuData = new ArrayList<>();Map<String, Object> menu1 = new HashMap<>();menu1.put("id", "1");menu1.put("parentId", "0");menu1.put("name", "系统管理");menu1.put("sort", 1);Map<String, Object> menu2 = new HashMap<>();menu2.put("id", "2");menu2.put("parentId", "1");menu2.put("name", "用户管理");menu2.put("sort", 1);menuData.add(menu1);menuData.add(menu2);// 构建树List<Tree<String>> trees = TreeUtil.build(menuData, "0",(treeNode, tree) -> {tree.setId(treeNode.get("id").toString());tree.setName(treeNode.get("name").toString());tree.setParentId(treeNode.get("parentId").toString());tree.putExtra("sort", treeNode.get("sort"));});System.out.println(JSONUtil.toJsonPrettyStr(trees)); }
方案三:使用注解方式(推荐)
// 使用 Hutool 的 @Alias 注解简化字段映射 @Data public class MenuVO { @Alias("code") // 对应原始数据的 code 字段 private String id;
@Alias("parent_code") // 对应原始数据的 parent_code 字段 private String parentId;@Alias("name") private String name;@Alias("sort") private Integer sort;private List<MenuVO> children;
public class AnnotationDemo {
public void buildTreeWithAnnotation() {// 原始数据(数据库查询结果)List<Map<String, Object>> rawData = new ArrayList<>();Map<String, Object> data1 = new HashMap<>();data1.put("code", "1");data1.put("parent_code", "0");data1.put("name", "系统管理");data1.put("sort", 1);Map<String, Object> data2 = new HashMap<>();data2.put("code", "2");data2.put("parent_code", "1");data2.put("name", "用户管理");data2.put("sort", 1);rawData.add(data1);rawData.add(data2);// 转换为 MenuVO 列表List<MenuVO> menuList = CollUtil.map(rawData, map -> {MenuVO vo = new MenuVO();BeanUtil.fillBeanWithMap(map, vo, true);return vo;}, true);// 构建树List<Tree<String>> trees = TreeUtil.build(menuList, "0");System.out.println(JSONUtil.toJsonPrettyStr(trees)); }
方案四:在 Spring Boot Controller 中使用
@GetMapping("/tree") public ResponseEntity<List<Tree<String>>> getMenuTree() {try {List<Menu> menuList = getMenuListFromDB();// 一行代码搞定树形结构!List<Tree<String>> menuTree = TreeUtil.build(menuList, "0");return ResponseEntity.ok(menuTree);} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();} }@GetMapping("/tree-with-config") public ResponseEntity<List<Tree<String>>> getMenuTreeWithConfig() {List<Menu> menuList = getMenuListFromDB();// 带配置的构建List<Tree<String>> trees = TreeUtil.build(menuList, "0",(treeNode, tree) -> {// 可以设置节点文本格式化等tree.setName(treeNode.getName() + "(" + treeNode.getId() + ")");tree.putExtra("type", "menu");tree.putExtra("createTime", new Date());},(treeNode1, treeNode2) -> {// 自定义排序规则Integer sort1 = (Integer) treeNode1.getExtra().get("sort");Integer sort2 = (Integer) treeNode2.getExtra().get("sort");return Integer.compare(sort1, sort2);});return ResponseEntity.ok(trees); }
方案五:其他实用方法
public class TreeUtilOtherMethods {
/*** 获取指定节点的所有子节点ID*/ public void getAllChildIds() {List<Menu> menuList = getMenuListFromDB();List<Tree<String>> trees = TreeUtil.build(menuList, "0");Tree<String> root = trees.get(0);List<Object> childIds = TreeUtil.getAllChildIdByBreadthFirst(root);System.out.println("所有子节点ID: " + childIds); }/*** 根据ID查找节点*/ public void findNodeById() {List<Menu> menuList = getMenuListFromDB();List<Tree<String>> trees = TreeUtil.build(menuList, "0");Tree<String> found = TreeUtil.getTreeNodeById(trees, "2");System.out.println("找到节点: " + found); }/*** 过滤树节点*/ public void filterTree() {List<Menu> menuList = getMenuListFromDB();List<Tree<String>> trees = TreeUtil.build(menuList, "0");// 只保留包含"管理"的节点List<Tree<String>> filtered = TreeUtil.filter(trees,node -> node.getName().contains("管理"));System.out.println("过滤后的树: " + JSONUtil.toJsonPrettyStr(filtered)); }
Hutool TreeUtil 的优势
简单易用:几行代码即可完成复杂树形构建
功能丰富:支持排序、过滤、查找等多种操作
灵活配置:支持自定义节点映射和排序规则
性能优秀:底层使用高效的算法实现
零依赖:Hutool 的其他模块已经包含了所需功能
总结
使用 Hutool 的 TreeUtil 可以大大简化树形结构的构建工作:
// 最简单的用法 List<Tree> tree = TreeUtil.build(menuList, "0");
// 如果需要排序,确保实体类实现了 Comparable 接口或使用 weight 字段
强烈推荐使用 Hutool 的 TreeUtil,它比手动实现更简洁、更可靠、功能更强大!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/982744.shtml