本文目录导读:

我来介绍几种在Java中构建菜单树形结构的常见方法:
基本数据模型定义
// 菜单实体类
public class Menu {
private Long id;
private String name;
private Long parentId;
private String url;
private Integer sort;
private List<Menu> children; // 子菜单列表
// getters/setters 省略
public Menu(Long id, String name, Long parentId, String url, Integer sort) {
this.id = id;
this.name = name;
this.parentId = parentId;
this.url = url;
this.sort = sort;
}
}
方法一:递归构建树形
public class MenuTreeBuilder {
/**
* 递归构建菜单树
*/
public static List<Menu> buildTree(List<Menu> menuList) {
List<Menu> tree = new ArrayList<>();
// 找出所有根节点(parentId为null或0)
for (Menu menu : menuList) {
if (menu.getParentId() == null || menu.getParentId() == 0) {
tree.add(findChildren(menu, menuList));
}
}
// 按排序字段排序
tree.sort(Comparator.comparing(Menu::getSort));
return tree;
}
private static Menu findChildren(Menu parent, List<Menu> menuList) {
List<Menu> children = new ArrayList<>();
for (Menu menu : menuList) {
if (parent.getId().equals(menu.getParentId())) {
children.add(findChildren(menu, menuList));
}
}
// 子节点排序
children.sort(Comparator.comparing(Menu::getSort));
parent.setChildren(children);
return parent;
}
}
方法二:使用Map优化性能
public class MenuTreeBuilderV2 {
/**
* 使用Map优化,O(n)复杂度
*/
public static List<Menu> buildTreeOptimized(List<Menu> menuList) {
List<Menu> tree = new ArrayList<>();
Map<Long, Menu> menuMap = new HashMap<>();
// 1. 先转换为Map
for (Menu menu : menuList) {
menu.setChildren(new ArrayList<>());
menuMap.put(menu.getId(), menu);
}
// 2. 构建树形关系
for (Menu menu : menuList) {
Long parentId = menu.getParentId();
if (parentId != null && menuMap.containsKey(parentId)) {
// 如果有父节点,添加到父节点的children中
Menu parent = menuMap.get(parentId);
parent.getChildren().add(menu);
} else {
// 没有父节点或父节点不在列表中,作为根节点
tree.add(menu);
}
}
// 3. 排序
sortMenuTree(tree);
return tree;
}
private static void sortMenuTree(List<Menu> menus) {
menus.sort(Comparator.comparing(Menu::getSort));
for (Menu menu : menus) {
if (menu.getChildren() != null && !menu.getChildren().isEmpty()) {
sortMenuTree(menu.getChildren());
}
}
}
}
方法三:Stream API方式
public class MenuTreeBuilderV3 {
/**
* 使用Stream API构建树
*/
public static List<Menu> buildTreeWithStream(List<Menu> menuList) {
Map<Long, List<Menu>> groupByParent = menuList.stream()
.collect(Collectors.groupingBy(
menu -> menu.getParentId() != null ? menu.getParentId() : 0L
));
return menuList.stream()
.filter(menu -> menu.getParentId() == null || menu.getParentId() == 0)
.peek(menu -> menu.setChildren(getChildren(menu.getId(), groupByParent)))
.sorted(Comparator.comparing(Menu::getSort))
.collect(Collectors.toList());
}
private static List<Menu> getChildren(Long parentId,
Map<Long, List<Menu>> groupByParent) {
List<Menu> children = groupByParent.getOrDefault(parentId, new ArrayList<>());
children.sort(Comparator.comparing(Menu::getSort));
children.forEach(child ->
child.setChildren(getChildren(child.getId(), groupByParent))
);
return children;
}
}
完整的测试示例
public class MenuTreeDemo {
public static void main(String[] args) {
// 模拟数据库查询出的菜单列表
List<Menu> menuList = Arrays.asList(
new Menu(1L, "系统管理", null, null, 1),
new Menu(2L, "用户管理", 1L, "/user", 1),
new Menu(3L, "角色管理", 1L, "/role", 2),
new Menu(4L, "权限管理", 1L, "/permission", 3),
new Menu(5L, "业务管理", null, null, 2),
new Menu(6L, "订单管理", 5L, "/order", 1),
new Menu(7L, "商品管理", 5L, "/product", 2),
new Menu(8L, "商品列表", 7L, "/product/list", 1),
new Menu(9L, "商品分类", 7L, "/product/category", 2)
);
// 构建树形
List<Menu> tree = MenuTreeBuilderV2.buildTreeOptimized(menuList);
// 输出树形结构
printMenuTree(tree, 0);
}
private static void printMenuTree(List<Menu> menus, int level) {
String indent = " ".repeat(level);
for (Menu menu : menus) {
System.out.println(indent + "├─ " + menu.getName() +
(menu.getUrl() != null ? " (" + menu.getUrl() + ")" : ""));
if (menu.getChildren() != null && !menu.getChildren().isEmpty()) {
printMenuTree(menu.getChildren(), level + 1);
}
}
}
}
输出结果示例
├─ 系统管理
├─ 用户管理 (/user)
├─ 角色管理 (/role)
├─ 权限管理 (/permission)
├─ 业务管理
├─ 订单管理 (/order)
├─ 商品管理 (/product)
├─ 商品列表 (/product/list)
├─ 商品分类 (/product/category)
Spring Boot中的实际应用
@RestController
@RequestMapping("/api/menu")
public class MenuController {
@Autowired
private MenuService menuService;
@GetMapping("/tree")
public Result<List<Menu>> getMenuTree() {
List<Menu> menuList = menuService.list(); // 从数据库查询所有菜单
List<Menu> tree = MenuTreeBuilderV2.buildTreeOptimized(menuList);
return Result.success(tree);
}
/**
* 返回EasyUI、Layui等前端框架需要的格式
*/
@GetMapping("/easyui-tree")
public Result<List<EasyUITreeNode>> getEasyUITree() {
List<Menu> menuList = menuService.list();
List<EasyUITreeNode> tree = convertToEasyUITree(menuList);
return Result.success(tree);
}
private List<EasyUITreeNode> convertToEasyUITree(List<Menu> menuList) {
// 适配前端框架的格式转换
// ...
}
}
建议:
- 数据量小时使用递归方式,代码清晰易懂
- 数据量大时使用Map方式,性能更好
- 可以结合缓存机制,减少频繁构建树形结构
- 注意处理循环引用的问题