优雅的封装构建树形结构工具类

我的博客:夜航猩-Space (cceven.cc)

序言

在软件开发中,树形结构是一种常见的数据结构,用于表示层级关系的数据。例如,组织结构、商品分类、菜单权限等数据经常需要以树形结构的形式展示和操作。在实际项目中,我们需要构建树形结构并进行操作,为了提高代码的复用性和可维护性,我们可以编写一个通用的方法来构建树形结构,并通过优化来提高其效率。

接下来就通过我对代码的一步步优化来实现如何从单一入参构建单一类型树形结构数据到通用树形构建的过程

业务背景

  1. 查询菜单列表,并构建树形结构然后返回给前端渲染
  2. 查询部门列表, 并构建属性结构然后返回给前端渲染
  • 以上两个场景可是大家在做A端或B端应用常见的使用场景了,我一开始是如何实现的呢

一代目

java 复制代码
// 构建部门树
 public static List<SysDepartmentVO> buildDepTree(List<SysDepartmentVO> source){
        if (CollUtil.isEmpty(source)) {
            return source;
        }
        return source.stream()
                .filter(dept -> Objects.equals(dept.getParentId(), NumberConstant.L_ZERO))
                .map(dept -> {
                    SysDepartmentVO root = new SysDepartmentVO();
                    BeanUtil.copyProperties(dept, root);
                    root.setChildren(getChildren(dept, source));
                    return root;
                })
                .collect(Collectors.toList());
    }
	
    // 构建菜单树
    public static List<SysMenuVO> buildMenuTree(List<SysMenuVO> menuList) {
        if (CollUtil.isEmpty(menuList)) {
            return menuList;
        }
        return menuList.stream()
                .filter(menu -> Objects.equals(menu.getParentId(), NumberConstant.L_ZERO))
                .map(menu -> {
                    SysMenuVO root = new SysMenuVO();
                    BeanUtil.copyProperties(menu, root);
                    root.setChildren(getChildren(menu, menuList));
                    return root;
                })
                .collect(Collectors.toList());
    }

 	// 查找 当前菜单下所有子菜单
    private static List<SysMenuVO> getChildren(SysMenuVO menu, List<SysMenuVO> menuList) {
        return menuList.stream()
                .filter(child -> child.getParentId().equals(menu.getId()))
                .map(child -> {
                    SysMenuVO childNode = new SysMenuVO();
                    BeanUtil.copyProperties(child, childNode);
                    // 此处递归
                    childNode.setChildren(getChildren(child, menuList));
                    return childNode;
                })
                .collect(Collectors.toList());
    }
	 // 查找 当前菜单下所有子菜单
    private static List<SysDepartmentVO> getChildren(SysDepartmentVO menu, List<SysDepartmentVO> menuList) {
        return menuList.stream()
                .filter(child -> child.getParentId().equals(menu.getId()))
                .map(child -> {
                    SysDepartmentVO childNode = new SysDepartmentVO();
                    // 此处递归
                    BeanUtil.copyProperties(child, childNode);
                    childNode.setChildren(getChildren(child, menuList));
                    return childNode;
                })
                .collect(Collectors.toList());
    }

分析

可以看到以上代码中包含两个主要方法 buildDepTreebuildMenuTree , 他们分别对各自的原始集合进行树结构封装, 细心的同学会发现, 他们除了入参集合中类型不同,其余代码均相同, 虽然确实实现了对应的功能,但是有着高度代码洁癖的我是不能容忍这样的代码出自于我手.

总结

经过刚才的分析,我们已经确定了这段代码的特性: 1, 相同的实现逻辑 2, 不同的入参类型

灵光一闪

此时脑海里你会想到什么? 没错就是 泛型

也有痛点

为什么说有痛点呢? 用泛型,还要高度抽象不可避免的就要使用到反射, 比如你要获取当前节点的 idparentId 来确定节点间子父级关系, 还需要获取 children 来对子节点进行赋值. 无形间增加了代码的复杂度并且过度使用泛型影响代码执行效率

确定方案

首先肯定使用泛型的,接下来就是考虑如何避免或不使用 反射 来达成目的, 你们想到了什么? 没错 接口或者抽象类啊! 于是 我定义一个树节点的接口 BaseNodeTree<T>,并定义了必要的方法,getId()getParentId()setChildren()等。然后,通过递归的方式构建树形结构,将每个节点的子节点递归添加到对应的父节点中。

二代目-实现

BaseNodeTree

java 复制代码
public interface BaseTreeNode<T> {

    /**
     * 获取id
     * @return 当前记录id
     */
    Long getId();

    /**
     * 获取父id
     *
     * @return 当前记录父id
     */
    Long getParentId();

    /**
     * 设置子节点
     * 
     * @param children  子节点集合
     */
    void setChildren(List<T> children);


}

对应实体类

伪代码, 仅做展示示例

typescript 复制代码
public class SysMenuVO  implements BaseTreeNode<SysMenuVO> {

    private Long id;

    private Long parentId;

    private List<SysMenuVO> children;

    @Override
    public Long getId() {
        return null;
    }

    @Override
    public Long getParentId() {
        return null;
    }

    @Override
    public void setChildren(List<SysMenuVO> child) {
        children = child;
    }
}

构建树

java 复制代码
// 构建树
public static <T extends BaseTreeNode<T>> List<T> buildTree(List<T> nodeList) {
        if (CollUtil.isEmpty(nodeList)) {
            return nodeList;
        }
        // 找父级
        return nodeList.stream()
                .filter(node -> Objects.equals(node.getParentId(), NumberConstant.L_ZERO))
                .map(node -> {
                    T root = BeanUtil.toBean(node, node.getClass());
                    // 找子集
                    root.setChildren(getChildren(node, nodeList));
                    return root;
                })
                .collect(Collectors.toList());
    }

	// 查询子集
    private static <T extends BaseTreeNode<T>> List<T> getChildren(T node, List<T> nodeList) {
        return nodeList.stream()
                .filter(child -> child.getParentId().equals(node.getId()))
                .map(child -> {
                    T childNode = BeanUtil.toBean(child, child.getClass());
                    childNode.setChildren(getChildren(child, nodeList));
                    return childNode;
                })
                .collect(Collectors.toList());
    }

小结

于是, 我们就抽象了一个公共的封装树形数据的方法, 妙妙妙! 到此, 可以说已经结束了, 但是代码看着还是有点长, 不太舒服

于是....

究极进化-完全体

废话不多说, 上代码

java 复制代码
public static <T extends BaseTreeNode<T>> List<T> buildTree(List<T> nodeList) {
        if (CollUtil.isEmpty(nodeList)) {
            return nodeList;
        }
        // 以父id进行分组
        Map<Long, List<T>> map = nodeList.stream()
                .collect(Collectors.groupingBy(BaseTreeNode::getParentId));
        // 构建树
        return buildTreeNodes(map, NumberConstant.L_ZERO);
    }

    private static <T extends BaseTreeNode<T>> List<T> buildTreeNodes(Map<Long, List<T>> map, Long parentId) {
        List<T> nodes = map.get(parentId);
        if (nodes == null) {
            return new ArrayList<>();
        }
        return nodes.stream()
                .map(node -> {
                    @SuppressWarnings("unchecked")
                    T newNode = (T) BeanUtil.toBean(node, node.getClass());
                    newNode.setChildren(buildTreeNodes(map, node.getId()));
                    return newNode;
                })
                .toList();
    }

应用场景

这种通用的构建树形结构方法在实际项目中有着广泛的应用场景。例如,在电商系统中,可以用于构建商品分类树;在企业管理系统中,可以用于构建组织结构树;在权限管理系统中,可以用于构建菜单权限树等。通过这种方法,可以快速、灵活地构建和管理各种树形结构,提高系统的可扩展性和可维护性。

总结

通过构建通用的树形结构方法,我们可以提高代码的复用性和可维护性,同时通过优化提高效率。在实际项目中,可以根据具体需求对这个方法进行定制和扩展,以满足不同场景的需求。希望本文对您理解和使用树形结构方法有所帮助,欢迎交流讨论

版权申明

  • 文章作者: 夜航猩
  • 文章来源: 夜航猩-Space
  • 版权声明: 转载请附上原文出处链接及本声明。
相关推荐
考虑考虑1 小时前
Jpa使用union all
java·spring boot·后端
用户3721574261351 小时前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊2 小时前
Java学习第22天 - 云原生与容器化
java
渣哥4 小时前
原来 Java 里线程安全集合有这么多种
java
间彧4 小时前
Spring Boot集成Spring Security完整指南
java
间彧5 小时前
Spring Secutiy基本原理及工作流程
java
Java水解6 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆8 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学8 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole8 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端