构建树形结构的通用Java方法:轻松处理层级数据

引言

在软件开发中,我们经常需要处理具有层级关系的数据,如组织架构、评论系统、分类目录等。这些数据通常以树形结构来表示,其中每个节点可以有零个或多个子节点。然而,数据库中的层级数据往往是以扁平表的形式存储的,这就需要我们在应用层将这些数据转换成树形结构。为了解决这个问题,我编写了一个通用的Java方法,可以自动将扁平数据转换成树形结构。

方法介绍

下面,我将详细介绍这个方法,它位于一个名为BuildTree的类中,方法名为buildTree。这个方法接受四个参数:

  1. list:包含所有节点的扁平列表。
  2. idFieldName:节点ID的字段名。
  3. pidFieldName:父节点ID的字段名。
  4. childrenFieldName:子节点列表的字段名。

方法的返回类型是List<T>,表示根节点的列表。

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * @ClassName buildTree
 * @Description
 * @Author @rwq
 * @Date 2024/12/16 17:12 星期一
 * @Version 1.0
 */


public class BuildTree {
    public static <T> List<T> buildTree(List<T> list, String idFieldName, String pidFieldName, String childrenFieldName)
            throws NoSuchFieldException, IllegalAccessException {
        Map<String, T> nodeMap = new HashMap<>();
        List<T> rootNodes = new ArrayList<>();

        // 将所有节点放入Map中,key为节点的id
        for (T node : list) {
            Field idField = node.getClass().getDeclaredField(idFieldName);
            idField.setAccessible(true);
            String id = (String) idField.get(node);
            nodeMap.put(id, node);
        }

        // 构建树结构
        for (T node : list) {
            Field pidField = node.getClass().getDeclaredField(pidFieldName);
            pidField.setAccessible(true);
            String pid = (String) pidField.get(node);

            T parentNode = nodeMap.get(pid);
            if (parentNode == null) {
                // 没有父节点,作为根节点
                rootNodes.add(node);
            } else {
                // 获取父节点的children字段
                Field childrenField = parentNode.getClass().getDeclaredField(childrenFieldName);
                childrenField.setAccessible(true);

                // 获取父节点的children列表,如果不存在则初始化
                @SuppressWarnings("unchecked")
                List<T> children = (List<T>) childrenField.get(parentNode);
                if (children == null) {
                    children = new ArrayList<>();
                    childrenField.set(parentNode, children);
                }
                // 将当前节点添加到父节点的children列表中
                children.add(node);
            }
        }
        return rootNodes;
    }
}
方法实现详解
  1. 节点映射:首先,我们创建一个HashMap,用于存储所有节点,其中key是节点的ID,value是节点对象本身。这样,我们可以快速通过ID查找节点。

  2. 构建树结构:然后,我们遍历扁平列表中的每个节点。对于每个节点,我们查找其父节点(通过父节点ID在映射中查找)。如果父节点不存在,说明该节点是根节点,我们将其添加到根节点列表中。如果父节点存在,我们获取父节点的子节点列表字段,如果不存在则初始化一个空列表,并将当前节点添加到该列表中。

  3. 返回根节点列表:最后,我们返回根节点列表,这些根节点及其子节点共同构成了完整的树形结构。

使用示例

假设我们有一个CommentsTreeVo类,表示评论树的节点,其中包含idparentIdchildren字段。

import com.baomidou.mybatisplus.annotation.*;
import com.fhs.core.trans.anno.Trans;
import com.fhs.core.trans.constant.TransType;
import com.github.appundefined.tree.TreeElement;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import vip.xiaonuo.common.pojo.CommonEntity;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 评论实体
 *
 * @author rwq
 * @date  2024/12/13 07:59
 **/
@Getter
@Setter
@ToString
public class CommentsTreeVo   {

    /** 评论id */
    @Schema(description = "评论id")
    private String id;
    
    //业务字段
    /** 收藏的用户id,(外键el_user用户表) */
    @Schema(description = "收藏的用户id,(外键el_user用户表)")
    private String userId;
    //业务字段
    /** 收藏的动态id(外键动态表) */
    @Schema(description = "收藏的动态id(外键动态表)")
    private String postId;

    /** 父评论ID,NULL表示顶级评论 */
    @Schema(description = "父评论ID,NULL表示顶级评论")
    private String parentId;
    //业务字段
    @Schema(description = "内容")
    private String content;

    private List<CommentsTreeVo> children = new ArrayList<>();
    //以下都是业务字段
    /** 创建时间(评论时间) */
    @Schema(description = "创建时间(评论时间)")
    private Date createTime;

    /** 删除标志 */
    @Schema(description = "删除标志")
    private String deleteFlag;


    /** 创建人 */
    @Schema(description = "创建人")
    private String createUser;

    /** 创建人名称 */
    @Schema(description = "创建人名称")
    private String createUserName;

    /** 更新时间 */
    @Schema(description = "更新时间")
    private Date updateTime;

    /** 更新人 */
    @Schema(description = "更新人")
    private String updateUser;

    /** 更新人名称 */
    @Schema(description = "更新人名称")
    private String updateUserName;
}

我们可以使用buildTree方法将评论列表转换成评论树:

List<CommentsTreeVo> comments = // 从数据库获取的评论列表(待处理的数据)
List<CommentsTreeVo> tree = BuildTree.buildTree(comments, "id", "parentId", "children");

这样,tree就包含了转换后的评论树形结构,我们可以轻松地进行后续操作,如遍历、展示等。

注意事项
  1. 字段访问:由于buildTree方法使用了反射来访问对象的字段,因此可能会抛出NoSuchFieldExceptionIllegalAccessException。在实际使用中,我们需要确保传入的字段名在节点类中确实存在,并且具有相应的访问权限。

  2. 性能考虑:反射操作相对较慢,如果数据量大或者需要频繁进行树形结构转换,可以考虑使用其他更高效的方案,如手动编写转换逻辑或使用专门的库。

  3. 泛型限制buildTree方法使用了泛型,可以处理任意类型的节点对象。但是,这也意味着我们无法在方法内部对节点对象进行具体的操作(如设置默认值、进行校验等),这些操作需要在调用方法之前或之后进行。

/* * 基于链表实现树结构 */ package dsa; public class TreeLinkedList implements Tree { private Object element;//树根节点 private TreeLinkedList parent, firstChild, nextSibling;//父亲、长子及最大的弟弟 //(单节点树)构造方法 public TreeLinkedList() { this(null, null, null, null); } //构造方法 public TreeLinkedList(Object e, TreeLinkedList p, TreeLinkedList c, TreeLinkedList s) { element = e; parent = p; firstChild = c; nextSibling = s; } /*---------- Tree接口中各方法的实现 ----------*/ //返回当前节点中存放的对象 public Object getElem() { return element; } //将对象obj存入当前节点,并返回此前的内容 public Object setElem(Object obj) { Object bak = element; element = obj; return bak; } //返回当前节点的父节点;对于根节点,返回null public TreeLinkedList getParent() { return parent; } //返回当前节点的长子;若没有孩子,则返回null public TreeLinkedList getFirstChild() { return firstChild; } //返回当前节点的最大弟弟;若没有弟弟,则返回null public TreeLinkedList getNextSibling() { return nextSibling; } //返回当前节点后代元素的数目,即以当前节点为根的子树的规模 public int getSize() { int size = 1;//当前节点也是自己的后代 TreeLinkedList subtree = firstChild;//从长子开始 while (null != subtree) {//依次 size += subtree.getSize();//累加 subtree = subtree.getNextSibling();//所有孩子的后代数目 } return size;//即可得到当前节点的后代总数 } //返回当前节点的高度 public int getHeight() { int height = -1; TreeLinkedList subtree = firstChild;//从长子开始 while (null != subtree) {//依次 height = Math.max(height, subtree.getHeight());//在所有孩子中取最大高度 subtree = subtree.getNextSibling(); } return height+1;//即可得到当前节点的高度 } //返回当前节点的深度 public int getDepth() { int depth = 0; TreeLinkedList p = parent;//从父亲开始 while (null != p) {//依次 depth++; p = p.getParent();//访问各个真祖先 } return depth;//真祖先的数目,即为当前节点的深度 } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值