Java中递归实现树形菜单数据结构的两种主流方案
针对前端展示需求,将数据库中扁平化的菜单列表组装为包含父子关系的JSON树形结构是Java开发中的常见任务。核心思路在于定义统一的数据模型,通过识别父级ID匹配子节点进行递归追加,直到末端节点返回空集合或自身。
一、 核心数据结构设计
无论是使用基础字符串标识还是整数主键,树形结构都需要一个包含自身属性及子节点列表的实体类。以下展示两种典型的 Java 实体定义方式。
第1步:定义基础版 Menu 实体
适用于 ID 类型不固定或字符串形式的场景。需确保包含 id、parentId、text 显示名以及 children 容器。
import java.util.List;
public class Menu {
private String id;
private String parentId;
private String text;
private String url;
private String yxbz;
// 用于存储子节点的列表
private List<Menu> children;
public Menu(String id, String parentId, String text, String url, String yxbz) {
this.id = id;
this.parentId = parentId;
this.text = text;
this.url = url;
this.yxbz = yxbz;
}
/* 省略 Getter/Setter 方法 */
}
第2步:定义标准数据库映射版 Menu 实体
若底层数据来自 MySQL 等关系型数据库,通常 ID 为自增整数。需注意父节点 ID 的判断逻辑差异(Integer vs String "0")。
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
public class Menu implements Serializable {
private static final long serialVersionUID = -5990021029947688358L;
private Integer id;
private String title;
private String path;
private Integer pid; // 父菜单ID,顶级菜单通常为 null 或 0
private Integer level;
private List<Menu> children = new ArrayList<>();
// 对应 SQL 建表参考:
// CREATE TABLE sys_menu (
// id int(11) NOT NULL AUTO_INCREMENT,
// pid bigint(20) DEFAULT NULL,
// title varchar(255) DEFAULT NULL,
// path varchar(255) DEFAULT NULL,
// PRIMARY KEY (id)
// ) ENGINE=InnoDB;
二、 递归树形结构生成算法
构造树的核心难点在于:先找出所有根节点,再遍历列表为每个节点寻找其子节点并挂载。
第3步:实现 MenuTree 工具类(基础String版)
该方法封装了初始化列表、提取根节点以及递归填充子树的完整流程。
import java.util.ArrayList;
import java.util.List;
public class MenuTree {
private List<Menu> menuList;
public MenuTree(List<Menu> menuList) {
this.menuList = menuList;
}
// 入口方法:汇总所有根节点并挂接子树
public List<Menu> builTree() {
List<Menu> treeMenus = new ArrayList<>();
for (Menu rootNode : getRootNode()) {
treeMenus.add(buildChilTree(rootNode));
}
return treeMenus;
}
// 获取根节点逻辑:父ID为特定值(如 "0")视为根
private List<Menu> getRootNode() {
List<Menu> rootMenuLists = new ArrayList<>();
for (Menu menu : menuList) {
if (menu.getParentId().equals("0")) {
rootMenuLists.add(menu);
}
}
return rootMenuLists;
}
// 递归核心:查找 pNode 下的所有直接子节点
private Menu buildChilTree(Menu pNode) {
List<Menu> chilMenus = new ArrayList<>();
for (Menu menu : menuList) {
if (menu.getParentId().equals(pNode.getId())) {
chilMenus.add(buildChilTree(menu)); // 递归调用
}
}
pNode.setChildren(chilMenus);
return pNode;
}
}
第4步:优化版递归逻辑(对象引用处理版)
在处理 Integer 类型 pid 或存在 null 值的场景中,建议使用 Objects.equals 避免空指针异常,同时优化内存引用效率。
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
// 假设 parseMenuTree 和 recursiveTree 位于同一个 Service 或 Utils 类中
/**
* 组装菜单树
*/
public static List<Menu> parseMenuTree(List<Menu> list) {
List<Menu> result = new ArrayList<>();
// 1. 筛选第一级节点(父ID为null的情况)
for (Menu menu : list) {
if (null == menu.getPid()) {
result.add(menu);
}
}
// 2. 对每个一级节点执行递归挂载
for (Menu parent : result) {
recursiveTree(parent, list);
}
return result;
}
private static Menu recursiveTree(Menu parent, List<Menu> list) {
for (Menu child : list) {
// 注意:比较 parent 的ID 是否等于 child 的PID
if (Objects.equals(parent.getId(), child.getPid())) {
recursiveTree(child, list); // 深入递归
parent.getChildren().add(child); // 将已处理好的子节点加入父节点列表
}
}
return parent;
}
三、 数据测试与验证
使用阿里巴巴 Fastjson 库可将生成的树形结构序列化为 JSON,便于观察层级关系是否正确。
import com.alibaba.fastjson.JSON;
import java.util.ArrayList;
import java.util.List;
public class TestDemo {
public static void main(String[] args) {
List<Menu> menuList = new ArrayList<>();
// 插入模拟数据
menuList.add(new Menu("GN001D000", "0", "系统管理", "/admin", "Y"));
menuList.add(new Menu("GN001D100", "GN001D000", "权限管理", "/admin", "Y"));
menuList.add(new Menu("GN001D110", "GN001D100", "密码修改", "/admin", "Y"));
menuList.add(new Menu("GN002D000", "0", "订阅区", "/admin", "Y"));
// 调用树构建器
MenuTree menuTree = new MenuTree(menuList);
List<Menu> treeResult = menuTree.builTree();
// 输出验证结果
System.out.println(JSON.toJSONString(treeResult));
}
}
四、 关键注意事项与排错
- ID 类型匹配:若数据库中使用
BIGINT或INT作为 PID,必须将判断条件从 `String` 改为Integer,且根节点通常对应 PID 为null而非0。 - 性能考量:上述递归方式为 O(N^2) 复杂度(N为节点数)。当菜单数量极大(上千级)时,建议改用 Map 索引法先将所有节点按 ID 放入 HashMap,再以 O(N) 复杂度通过引用直接挂载子节点。
- 空值处理:在进行字符串比较前务必检查父ID是否为 null,推荐使用
java.util.Objects.equals(a, b)安全比较。
以上就是Java递归生成树形菜单数据结构的详细内容,更多关于Java树形算法的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













