天天看点

Java构建树形结构

前言

  • 原始递归
  • 利用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处于中间层级)的情况下,构建树形结构会失败。