logo 范 · 拾光录
网址收集 关于作者 Github Gitee
杂文随笔5
Hexo博客:基础使用Hexo博客:Next主题Hexo博客:Next进阶使用Hexo博客:Next高级配置基于Node的WIKI管理
前端知识16
HTML常用知识CSS常用知识CSS美化checkbox复选框JavaScript常用知识JavaScript格式化时间戳JavaScript窗口宽高处理JavaScript黑夜主题切换实现方案JavaScript数字转大写简易图片查看器TypeScript基础知识Threejs基础三要素Threejs网格辅助和轨道控制器Threejs物体绘制Electron基础使用Nodejs基础知识animate.css页面动画
Vue框架19
Vite的使用及扩展Vue3父子组件Vue3使用Marked解析MarkdownMermaid图表生成库初始化页面加载动画Axios表单提交二维码解决方案NProgress加载进度条Vue3动态菜单实现Vue3使用ECharts图表Vue3处理Excel导入导出keep-alive页面缓存及setup问题Element:文件上传Element:结合Pinia实现动态菜单Element:图片上传组件Element:自定义统一弹窗组件Element:表格自定义指令控制按钮显示(鉴权)可视化大屏使用缩放适配分辨率
UniApp15
UniApp的基础使用封装网络请求工具及文件上传uni-app的开发记录微信小程序分享原生文件上传Pinia取消滚动条(兼容小程序)tabbar消息数量显示scroll-view上滑到底部加载数据状态栏高度动态设配数据共享与传递uview-plus导航栏实现背景融合Wot UIWot UI实现顶部背景图融合uni-app x
Java基础知识10
基础知识面向对象Lambda表达式常用API常用知识积累try-with-resource注解反射多线程经纬度距离计算
SpringBoot31
application配置Maven创建聚合项目全局异常处理锁机制项目启动初始化数据方式邮件功能集成原生定时任务异步集成阿里云OSS阿里OSS预签名上传基于hutool读excelJSR303WebSocketWebSocket版AI接口流式调用Smart-Doc接口文档生成器application配置信息加密雪花算法工具AOP实现请求参数脱敏思路JWT生成Token及工具类SpringBoot默认JSON与对象转换若依框架:安装使用若依框架:优化和调整文件上传若依框架:管理后台页面优化若依框架:后端接口代码优化SpringAISpringBoot实现AI接口流式调用服务启动时创建MySQL连接自建项目工程树形结构处理工具微信支付代码微信手机号登录
SpringMVC14
跨域处理拦截器RESTful风格伪前后端分离Jackson转换器调整Thymeleaf基于拦截器做权限校验AOP打印接口请求响应日志AOP打印接口请求响应耗时文件上传和回显POST请求加解密实现(AES)POST请求加解密实现(RSA+AES)参数动态校验实现方案真实IP和归属地
MyBatis8
MyBatis基本使用与配置Mapper使用相关MaBatis多数据源配置MyBatisPlus数据统计类处理方案MyBatisPlus条件查询正向工程的实现(H2)mybatis-plus-join
SpringCloud15
Netflix:微服务与搭建Netflix:服务的消费与提供Netflix:EurekaNetflix:ActuatorNetflix:RibbonNetflix:FeignNetflix:HystrixNetflix:ZuulAlibaba:简介与搭建Alibaba:Nacos注册中心Alibaba:RibbonAlibaba:OpenFeignAlibaba:Nacos配置中心Alibaba:GetewayAlibaba:Sentinel
MySQL6
MySQL基础知识MySQL多表查询与事务MySQL常用函数及解决方案MySQL视图MySQL索引安装MySQL
Redis7
Redis介绍和安装Redis配置文件Redis持久化Redis集群Redis语法基础Redis相关问题及解决方案SpringBoot集成Redis使用记录
MongoDB10
Linux安装MongoDBMongoDB基础语法MongoTemplate及SpringBoot配置MongoTemplate中Update操作MongoTemplate中聚合查询MongoTemplate日期归档示例项目使用相关知识归纳地理位置存储与距离查询MongoDB副本集与事务获取类名和属性名工具类
其他数据库1
H2数据库
Python编程6
Python基础知识Python语法yolo目标检测OpenCV的使用及树莓派平台condauv
工具集合13
IDEAMavenGradleGitNginx安装Nginx配置VSCodeJMeter压测DockerOllamaRustFSPicGoObs录制
Linux知识11
Linux常用命令Jar启动脚本VirtualBox安装CentOSVirtualBox安装Ubuntu树莓派安装及使用frp内网穿透ArchLinux:基础系统安装ArchLInux:图形化界面安装ArchLinux:常用软件ArchLinux:深度优化ArchLinux:Niri
创意设计2
Blender:入门知识UI设计基础知识
AI相关9
Claude CodeHermes AgentOpenAI基本使用OpenAI工具调用OpenAI记忆管理OpenAI推理执行OpenAI开发框架Langchainllama.cpp

作用

用于快速将带有树形结构的数据列表转为树结构,注意事项:

代码

package com.fan.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 树形数据处理工具类
 */
public class TreeDataUtil {

    private static final Logger logger = LoggerFactory.getLogger(TreeDataUtil.class);

    /**
     * 通用树形数据处理
     * 列表数据需要满足以下条件:
     * 1. 节点必须有 id 字段,用于唯一标识
     * 2. 节点必须有 parentId 字段,用于标识父节点
     * 3. 节点必须有 sort 字段,用于排序 (数值越大越靠前)
     *
     * @param allNodes 需要处理的平铺数据列表
     * @return 构建树形结构后的根节点列表 (List<T>)
     */
    public <T> List<T> listToTree(List<T> allNodes) {
        if (allNodes == null || allNodes.isEmpty()) {
            return new ArrayList<>();
        }

        // 1. 先进行排序:sort 字段降序排列 (越大越靠前)
        sortNodes(allNodes);

        // 2. 构建树形结构
        return buildTree(allNodes);
    }

    /**
     * 对节点列表进行排序
     */
    private <T> void sortNodes(List<T> allNodes) {
        try {
            // 检查是否包含 sort 字段,如果包含则排序
            allNodes.get(0).getClass().getDeclaredField("sort");

            // 执行排序
            allNodes.sort((o1, o2) -> {
                Object sort1 = getFieldValue(o1, "sort");
                Object sort2 = getFieldValue(o2, "sort");

                // 处理 null 值,默认视为 0
                Number n1 = sort1 == null ? 0 : (Number) sort1;
                Number n2 = sort2 == null ? 0 : (Number) sort2;

                // 降序:o2 - o1
                return Double.compare(n2.doubleValue(), n1.doubleValue());
            });
        } catch (NoSuchFieldException e) {
            // 如果实体类没有 sort 字段,则不进行排序,直接跳过
            logger.debug("Node class does not contain 'sort' field, skipping sorting.");
        } catch (Exception e) {
            logger.warn("Sorting failed: {}", e.getMessage());
        }
    }

    /**
     * 核心递归构建树的方法
     */
    private <T> List<T> buildTree(List<T> allNodes) {
        List<T> rootNodes = new ArrayList<>();

        // 使用 Map 存储 ID 到对象的映射,提高查找效率 O(1)
        Map<Object, T> nodeMap = new HashMap<>();
        for (T node : allNodes) {
            Object id = getFieldValue(node, "id");
            if (id != null) {
                nodeMap.put(id, node);
            }
        }

        // 遍历所有节点,将节点添加到其父节点的 children 列表中
        for (T node : allNodes) {
            Object parentId = getFieldValue(node, "parentId");

            // 判断是否为根节点
            if (isRoot(parentId)) {
                rootNodes.add(node);
            } else {
                // 尝试找到父节点
                T parent = nodeMap.get(parentId);
                if (parent != null) {
                    addChild(parent, node);
                }
                // 如果找不到父节点,视为上级已经被删除,下级不再展示
            }
        }

        return rootNodes;
    }

    /**
     * 判断是否为根节点
     */
    private boolean isRoot(Object parentId) {
        if (parentId == null) {
            return true;
        }
        String val = String.valueOf(parentId);
        return "0".equals(val) || "".equals(val);
    }

    /**
     * 利用反射获取字段值
     */
    private Object getFieldValue(Object obj, String fieldName) {
        try {
            Field field = obj.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            return field.get(obj);
        } catch (Exception e) {
            // 尝试通过 Getter 方法获取
            try {
                String getterName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
                Method method = obj.getClass().getMethod(getterName);
                return method.invoke(obj);
            } catch (Exception ex) {
                // 静默处理,返回 null
                return null;
            }
        }
    }

    /**
     * 利用反射将子节点添加到父节点的 children 列表中
     */
    @SuppressWarnings("unchecked")
    private void addChild(Object parent, Object child) {
        try {
            Field childrenField = parent.getClass().getDeclaredField("children");
            childrenField.setAccessible(true);

            // 获取当前的 children 对象
            Object childrenObj = childrenField.get(parent);
            List<Object> children;

            // 安全检查:确保它是 List 类型
            if (childrenObj instanceof List) {
                // 这里利用泛型擦除,将 List<?> 视为原始 List 进行操作
                // 虽然会有 "unchecked" 警告,但比直接强转 Object 到 List<Object> 更安全
                children = (List<Object>) childrenObj;
            } else {
                // 如果为 null,初始化一个新的 ArrayList
                children = new ArrayList<>();
                childrenField.set(parent, children);
            }

            children.add(child);

        } catch (NoSuchFieldException e) {
            logger.warn("Target class [{}] is missing 'children' field.", parent.getClass().getName());
        } catch (IllegalAccessException e) {
            logger.error("Failed to access children field in class [{}]", parent.getClass().getName(), e);
        }
    }
}

使用方式

// 树形数据处理
List<TbAddressListResp> listTree = new TreeDataUtil().listToTree(list);
作用
代码
使用方式