Java中递归实现树形菜单数据结构的两种主流方案

Cora
Cora
发布时间:2026-10-16 | 4人浏览过
原创

针对前端展示需求,将数据库中扁平化的菜单列表组装为包含父子关系的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树形算法的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表