前言
平时大概率我们会构建一些树形结果返回给前端,比如菜单结构、部门列表、文件结构等,我们一般想到的就是利用递归来循环构建;现在,就我个人解决的方法如下:
- 原始递归
- 利用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
网友评论