美文网首页
Java构建树形结构

Java构建树形结构

作者: 小波同学 | 来源:发表于2022-05-23 15:49 被阅读0次

    前言

    平时大概率我们会构建一些树形结果返回给前端,比如菜单结构、部门列表、文件结构等,我们一般想到的就是利用递归来循环构建;现在,就我个人解决的方法如下:

    • 原始递归
    • 利用Java 8 Stream流进行处理(原理还是递归)
    • Stream流升级构建

    一、场景构建

    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    import lombok.EqualsAndHashCode;
    import lombok.experimental.Accessors;
    
    import java.io.Serializable;
    import java.util.List;
    
    /**
     * @author: huangyibo
     * @Date: 2022/5/23 15:13
     * @Description:
     */
    
    @Data
    @EqualsAndHashCode(callSuper = false)
    @Accessors(chain = true)
    @ApiModel(description="菜单权限")
    public class MenuVo implements Serializable {
    
        @ApiModelProperty(value = "菜单ID")
        private String id;
    
        @ApiModelProperty(value = "菜单名称")
        private String menuName;
    
        @ApiModelProperty(value = "父菜单ID")
        private String parentId;
    
        @ApiModelProperty(value = "显示顺序")
        private Integer orderNum;
    
        @ApiModelProperty(value = "路由地址")
        private String path;
    
        @ApiModelProperty(value = "组件路径")
        private String component;
    
        @ApiModelProperty(value = "菜单类型(M目录 C菜单 F按钮)")
        private String menuType;
    
        @ApiModelProperty(value = "菜单状态(显示=DISPLAY 隐藏=HIDE)")
        private String visible;
    
        @ApiModelProperty(value = "权限Code")
        private String authCode;
    
        @ApiModelProperty(value = "菜单图标")
        private String icon;
    
        @ApiModelProperty(value = "子菜单list")
        private List<MenuVo> childMenuList;
    }
    

    二、原始递归构建树

    import org.springframework.util.StringUtils;
    
    import java.util.ArrayList;
    import java.util.Comparator;
    import java.util.List;
    import java.util.stream.Collectors;
    
    /**
     * @author: huangyibo
     * @Date: 2022/5/23 15:17
     * @Description: 树形构造处理工具类
     */
    
    public class BuildTreeUtil {
    
        /**
         * 构建树形结构
         *
         * @param menus 菜单列表
         * @return 树结构列表
         */
        public static List<MenuVo> buildMenuTree(List<MenuVo> menus) {
            List<MenuVo> returnList = new ArrayList<>();
            List<String> tempList = new ArrayList<>();
            for (MenuVo dept : menus) {
                tempList.add(dept.getId());
            }
            for (MenuVo menu : menus) {
                // 如果是顶级节点, 遍历该父节点的所有子节点
                if (!tempList.contains(menu.getParentId())) {
                    recursionFn(menus, menu);
                    returnList.add(menu);
                }
            }
            if (returnList.isEmpty()) {
                returnList = menus;
            }
            return returnList;
        }
    
        /**
         * 递归列表
         *
         * @param list
         * @param menuVo
         */
        private static void recursionFn(List<MenuVo> list, MenuVo menuVo) {
            // 得到子节点列表
            List<MenuVo> childList = getChildList(list, menuVo);
            menuVo.setChildList(childList);
            for (MenuVo tChild : childList) {
                if (hasChild(list, tChild)) {
                    recursionFn(list, tChild);
                }
            }
        }
    
    
        /**
         * 得到子节点列表
         */
        private static List<MenuVo> getChildList(List<MenuVo> menuVoList, MenuVo menuVo) {
            List<MenuVo> list = new ArrayList<>();
            for (MenuVo menu : menuVoList) {
                if (!StringUtils.isEmpty(menu.getParentId()) && menu.getParentId().equals(menuVo.getId())) {
                    list.add(menu);
                }
            }
            //Comparator.nullsFirst和Comparator.nullsLast, 顾名思义一个把null放在最前, 一个把null放在最后. 解决两种空指针异常都要用这两个方法
            list = list.stream().sorted(Comparator.comparing(MenuVo::getOrderNum, Comparator.nullsLast(Integer::compareTo).reversed()).reversed()).collect(Collectors.toList());
            return list;
        }
    
    
        /**
         * 判断是否有子节点
         */
        private static boolean hasChild(List<MenuVo> list, MenuVo menuVo) {
            return getChildList(list, menuVo).size() > 0;
        }
    }
    

    二、利用Java 8 Stream流进行处理(原理还是递归)

    import org.springframework.util.StringUtils;
    
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    
    /**
     * @author: huangyibo
     * @Date: 2022/5/23 15:34
     * @Description: 树形构造处理工具类
     */
    
    public class BuildTreeUtil {
    
        
        /**
         * 构建树形结构
         * @param trees 菜单列表
         * @return 树结构列表
         */
        public static List<MenuVo> buildDeptTreeByStream(List<MenuVo> trees){
            //获取parentId 为空 的根节点
            List<MenuVo> list = trees.stream().filter(item -> StringUtils.isEmpty(item.getParentId())).collect(Collectors.toList());
            //根据parentId进行分组
            Map<String, List<MenuVo>> map = trees.stream().collect(Collectors.groupingBy(MenuVo::getParentId));
            recursionFnTree(list, map);
            return list;
        }
    
    
        /**
         * 递归遍历节点
         * @param list
         * @param map
         */
        public static void recursionFnTree(List<MenuVo> list, Map<String, List<MenuVo>> map){
            for (MenuVo menuVo : list) {
                List<MenuVo> childList = map.get(menuVo.getId());
                menuVo.setChildList(childList);
                if (null != childList && 0 < childList.size()){
                    recursionFnTree(childList,map);
                }
            }
        }
    }
    

    三、Stream流升级构建

    import org.springframework.util.StringUtils;
    
    import java.util.Comparator;
    import java.util.List;
    import java.util.Objects;
    import java.util.stream.Collectors;
    
    
    /**
     * @author: huangyibo
     * @Date: 2022/5/23 15:39
     * @Description: 树形构造处理工具类
     */
    
    public class BuildTreeUtil {
    
    
        /**
         * 构建树形结构
         * @param trees 菜单列表
         * @return 树结构列表
         */
        public static List<MenuVo> buildDeptTreeByStream(List<MenuVo> trees) {
            //获取parentId 为空 的根节点
            return trees.stream().filter(item -> StringUtils.isEmpty(item.getParentId()))
                    //Comparator.nullsFirst和Comparator.nullsLast, 顾名思义一个把null放在最前, 一个把null放在最后. 解决两种空指针异常都要用这两个方法
                    .sorted(Comparator.comparing(MenuVo::getOrderNum, Comparator.nullsLast(Integer::compareTo).reversed()).reversed())
                    .peek(item -> item.setChildList(getChildrenList(item, trees))).collect(Collectors.toList());
        }
    
    
        /**
         * 获取子节点列表
         * @param tree 菜单列表
         * @param list 子节点列表
         * @return
         */
        public static List<MenuVo> getChildrenList(MenuVo tree, List<MenuVo> list){
            return list.stream().filter(item -> Objects.equals(item.getParentId(), tree.getId()))
                    //Comparator.nullsFirst和Comparator.nullsLast, 顾名思义一个把null放在最前, 一个把null放在最后. 解决两种空指针异常都要用这两个方法
                    .sorted(Comparator.comparing(MenuVo::getOrderNum, Comparator.nullsLast(Integer::compareTo).reversed()).reversed())
                    .peek(item -> item.setChildList(getChildrenList(item, list))).collect(Collectors.toList());
        }
    }
    
    • 个人还是比较倾向用Stream流构建树形结构,节省代码量还通俗易懂!
    • Stream在实际开发过程中,运用得体的话,既能节省代码量,还能提高效率,但是复杂的流式处理数据也会让代码看起来不易理解!
    • 但是如果使用Stream流构建树形结构,在数据没有顶层目录(即parentId处于中间层级)的情况下,构建树形结构会失败。

    参考:
    https://blog.csdn.net/Y_hanxiong/article/details/124053067

    https://www.cnblogs.com/zhulei2/p/15689555.html

    相关文章

      网友评论

          本文标题:Java构建树形结构

          本文链接:https://www.haomeiwen.com/subject/ibvrprtx.html