java后端:菜单的生成与读取

2021-08-23  本文已影响0人  Qiansion齐木楠雄

概述

在后端开发的过程中,我们经常会遇到一类问题,那就是关于前端页面菜单的展示,一般来说,从数据库中获取到的是一个List集合,而我们需要将这个List集合根据菜单本身的id和它的parentId来把List转换成一个Tree,然后将这个Tree交给前端的框架进行展示。本篇主要针对List集合转换成Tree并遍历输出做一个介绍。

案例

1、首先定义实体类
getter和setter方法请自行定义

public class Menu {
    // 菜单id
    private String id;
    // 菜单名称
    private String name;
    // 父菜单id
    private String parentId;
    // 菜单顺序
    private int order;
    // 子菜单
    private List<Menu> childMenus;
}

2、初始化菜单
这里进行了简化,不从数据库里读取,直接人为制造。将数据添加到集合之后用Colletions进行了排序,也是为了模拟从数据库取出时的order by操作。

public class MenuDao {

    private static ArrayList<Menu> rootMenu = new ArrayList<>();
    static {
        Menu menu = new Menu("1", "-一级菜单1", null,  0);
        Menu menu1 = new Menu ("2", "-一级菜单2", null,  1);
        Menu menu2 = new Menu ("3", "--first二级菜单2", "2",  0);
        Menu menu3 = new Menu ("4", "--second二级菜单2", "2",  1);
        Menu menu4 = new Menu ("5", "--first二级菜单1", "1",  2);
        Menu menu5 = new Menu ("6", "---first三级菜单1", "5", 0);
        Menu menu6 = new Menu ("7", "---second三级菜单1", "5", 1);
        Menu menu7 = new Menu ("8", "----first四级菜单1", "7", 0);
        rootMenu.add(menu);
        rootMenu.add(menu1);
        rootMenu.add(menu2);
        rootMenu.add(menu3);
        rootMenu.add(menu4);
        rootMenu.add(menu5);
        rootMenu.add(menu6);
        rootMenu.add(menu7);
        Collections.sort(rootMenu, new Comparator<Menu>() {
            @Override
            public int compare(Menu o1, Menu o2) {
                return o1.getOrder() >= o2.getOrder()?1:-1;
            }
        });
    }

    public static ArrayList<Menu>  queryMenuList(){
        return rootMenu;
    }
}

这里根据菜单的id和parentId可以还原如下


image.png

需要注意此处有2个根节点,一般在项目中都是只有一个根节点的。

3、菜单的生成
菜单的生成主要包括两个方面
①找出所有根节点
②递归生成每个菜单的子菜单

    /**
     * 生成树形结构
     * @return
     */
    private static List<Menu> createListTree(){
        // 模拟从数据库取数据
        List<Menu> rootMenu = MenuDao.queryMenuList();
        List<Menu>  menuList = new ArrayList<>();
        for (Menu menu : rootMenu) {
            // 先找到所有根节点,根节点没有parentId
            if(menu.getParentId() == null){
                // 以根节点为起点向下递归查找自己的子类
                menu = buildChildTree(menu , rootMenu);
                menuList.add(menu);
            }
        }
        return menuList;
    }
    /**
     * 递归装载子类
     * @param menu  当前节点
     * @param rootMenu 菜单列表
     * @return
     */
    private static Menu buildChildTree(Menu menu, List<Menu> rootMenu) {
        // 用来存放自己的子节点
        List<Menu> childMenus = new ArrayList<>();
        for (Menu menuNode : rootMenu) {
            // 如果当前节点的id 与 菜单列表中其他节点的parentId相同,就讲其放入到childMenus中
            if(menuNode.getParentId()!=null && menuNode.getParentId().equals(menu.getId())){
                // 递归查询子节点的child
                childMenus.add(buildChildTree(menuNode,rootMenu));
            }
        }
        // 每一个递归完成,给当前节点设置子节点
        menu.setChildMenus(childMenus);
        return menu;
    }

递归有循环体和结束条件,在buildChildTree方法中结束条件不明显,因为每一次递归都会走到最后一步的return menu,所以再最终结果中所有菜单的子节点都会是[]而不是null。而且在这个递归中,其实就是从根节点开始一遍一遍的去跟菜单列表循环,找到菜单列表中parentId等于自己id的部分,但是它不是一次性就找完全部的,它找到子菜单之后,会再去看这个子菜单自己是否还会有子菜单,然后一遍遍套娃,直到它返回当前节点,然后可以继续寻找当前节点的下一个。举个例子,递归就像是看源码,一开始我们再看逻辑,突然看到一个方法,想知道它是怎么实现的,我们就点进去,然后不停的点,当我们又想继续看逻辑时,又必须一步一步的退回来。
4、编写客户端

public class Client {
    public static void main(String[] args) {
        List<Menu> listTree = createListTree();
        for (Menu menu : listTree) {
            System.out.println(menu);
        }
    }
}

Menu{id='1', name='-一级菜单1', parentId='null', order=0, childMenus=[Menu{id='5', name='--first二级菜单1', parentId='1', order=2, childMenus=[Menu{id='6', name='---first三级菜单1', parentId='5', order=0, childMenus=[]}, Menu{id='7', name='---second三级菜单1', parentId='5', order=1, childMenus=[Menu{id='8', name='----first四级菜单1', parentId='7', order=0, childMenus=[]}]}]}]}
Menu{id='2', name='-一级菜单2', parentId='null', order=1, childMenus=[Menu{id='3', name='--first二级菜单2', parentId='2', order=0, childMenus=[]}, Menu{id='4', name='--second二级菜单2', parentId='2', order=1, childMenus=[]}]}
可以看出返回的是两个根节点的部分,虽然看不出来是否的正确的(有条件的可以转化为JSON再去看)。此时还需要写一个输出根节点的方法

    /**
     * 从唯一根节点开始递归遍历
     * @param menu
     */
    private static void  OutputTreeData(Menu menu){
        System.out.println(menu.getName());
        if(menu.getChildMenus()==null || menu.getChildMenus().size()==0){
            return;
        }
        for(Menu item : menu.getChildMenus()){
            // 子节点如果还有孩子将继续递归
            OutputTreeData(item);
        }
    }

修改客户端代码

public class Client {
    public static void main(String[] args) {
        List<Menu> listTree = createListTree();
        for (Menu menu : listTree) {
             OutputTreeData(menu);
        }
    }
}

最终输出:
-一级菜单1
--first二级菜单1
---first三级菜单1
---second三级菜单1
----first四级菜单1
-一级菜单2
--first二级菜单2
--second二级菜单2

上一篇下一篇

猜你喜欢

热点阅读