java构建树结构的原理与工具类

下面的第一章节为构建树的原理,往下拉到文章末尾有工具类可以直接使用

构建树的原理:

先看看模拟的数据库的值(数据库名:company(公司))

可以根据parentId为0的数据为父节点,但是有些数据库根据不同的情况,设置了不同属性

level(等级1为顶点)idparentId(父公司Id)name(公司名)
110顶级节点A
120顶级节点B
231父节点是A
242父节点是B
252父节点是B
363父节点的ID是3

递归构建树

构建一棵树的步骤

1、首先获取所有的根节点(顶级节点),跟数据库的配置有关
2、根据每一个根节点,与所有节点集合(数据)进行判断,当前节点是否为其下的子节点。
3、若是,则递归调用构建树形;若不是,则表明该节点不属于其下子节点。
4、应继续循环判断节点父子关系,直到所有节点与根节点判断完毕。

   /**
     * 构造树(需要根据不同的情况变动)
     * @return
     */
    private List<TreeNode> constructionTree(List<Company> list) {
        // 全部数据(由查找的数据遍历得到)
        List<TreeNode> allList = new ArrayList<>();
        // 根节点的数据
        List<TreeNode> rootNodeList = new ArrayList<>();
        // 最终的树结构
        List<TreeNode> treeList = new ArrayList<>();
        // 获取根节点
        list.forEach(x -> {
            // 这里就是说根节点是级别为1的值
            if ("1".equals(x.getLevel())) {
                TreeNode treeNode = new TreeNode()
                        .setKey(x.getId())
                        .setId(x.getId())
                        .setTitle(x.getName())
                        .setParentId(x.getParentId());
                rootNodeList.add(treeNode);
            }
        	// 构造树结构,这里可以执行过滤操作,把list(List<Data>)过滤成我们需要的参数(allList)
            TreeNode treeNode = new TreeNode()
                    .setKey(x.getId())
                    .setId(x.getId())
                    .setTitle(x.getName())
                    .setParentId(x.getParentId());
            allList.add(treeNode);
        });
        // 装配树干
        for (TreeNode treeRootNode : rootNodeList) {
            // 将顶级节点进行构建子树
            treeRootNode = buildChildTree(treeRootNode,allList);
            // 完成一个顶级节点所构建的树形,增加进来
            treeList.add(treeRootNode);
        }
        return treeList;
    }
    /**
     *  递归-----构建子树形结构
     *  @param  pNode 根节点(顶级节点)
     *  @param  treeList 全部数据
     *  @return 整棵树
     */
    public TreeNode buildChildTree(TreeNode pNode, List<TreeNode> treeList){
        List<TreeNode> childTree = new ArrayList<>();
        // nodeList:所有节点集合(所有数据)
        for (TreeNode treeNode : treeList) {
            // 判断当前节点的父节点ID是否等于根节点的ID,即当前节点为其下的子节点
            if (treeNode.getParentId().equals(pNode.getId())) {
                // 再递归进行判断当前节点的情况,调用自身方法
                childTree.add(buildChildTree(treeNode,treeList));
            }
        }
        // for循环结束,即节点下没有任何节点,树形构建结束,设置树结果
        pNode.setChildren(childTree);
        return pNode;
    }

TreeNode(树节点类)(用于封装转给前端),里面的值可以改动

/**
 *  TreeNode 树节点
 *
 *  @author ZPA
 *  @date 2023年10月23日
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class TreeNode {

    /** ID(前端需要对应公司的id) */
    private String key;

    /** 节点id */
    private String id;

    /** 父节点id:顶级节点为 0 */
    private String parentId;

    /** 节点名称(前端需要对应公司的name) */
    private String title;

    /** 子节点 */
    private List<TreeNode> children;
}

Company(实体类)

/**
 * 模拟公司
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Company {
    /**
     * 公司等级
     */
    private String level;
    /**
     * 公司ID
     */
    private String id;
    /**
     * 父公司ID
     */
    private String parentId;
    /**
     * 公司名字
     */
    private String name;

}

测试

public class TreeTest {
    @Test
    public void treeTest(){
        // 模拟测试数据(模拟数据库的值)
        List<Company> companyList = new ArrayList<>();
        companyList.add(new Company("1","1","0","顶级节点A"));
        companyList.add(new Company("1","2","0","顶级节点B"));
        companyList.add(new Company("2","3","1","父节点是A"));
        companyList.add(new Company("2","4","2","父节点是B"));
        companyList.add(new Company("2","5","2","父节点是B"));
        companyList.add(new Company("3","6","3","父节点的ID是3"));

        
        TreeTest test = new TreeTest();
        // 原查询结果转换树形结构
        List<TreeNode> treeNodes = test.constructionTree(companyList);
        System.out.println(treeNodes);

    }

    /**
     * 构造树(需要根据不同的情况变动)
     * @return
     */
    private List<TreeNode> constructionTree(List<Company> list) {
        // 全部数据(由查找的数据遍历得到)
        List<TreeNode> allList = new ArrayList<>();
        // 根节点的数据
        List<TreeNode> rootNodeList = new ArrayList<>();
        // 最终的树结构
        List<TreeNode> treeList = new ArrayList<>();
        // 获取根节点
        list.forEach(x -> {
            // 这里就是说根节点是级别为1的值
            if ("1".equals(x.getLevel())) {
                TreeNode treeNode = new TreeNode()
                        .setKey(x.getId())
                        .setId(x.getId())
                        .setTitle(x.getName())
                        .setParentId(x.getParentId());
                rootNodeList.add(treeNode);
            }
        });
        // 构造树结构,这里可以执行过滤操作,把list(List<Data>)过滤成我们需要的参数(allList)
        list.forEach(x->{
            TreeNode treeNode = new TreeNode()
                    .setKey(x.getId())
                    .setId(x.getId())
                    .setTitle(x.getName())
                    .setParentId(x.getParentId());
            allList.add(treeNode);
        });
        // 装配树干
        for (TreeNode treeRootNode : rootNodeList) {
            // 将顶级节点进行构建子树
            treeRootNode = buildChildTree(treeRootNode,allList);
            // 完成一个顶级节点所构建的树形,增加进来
            treeList.add(treeRootNode);
        }
        return treeList;
    }
    /**
     *  递归-----构建子树形结构
     *  @param  pNode 根节点(顶级节点)
     *  @param  treeList 全部数据
     *  @return 整棵树
     */
    public TreeNode buildChildTree(TreeNode pNode, List<TreeNode> treeList){
        List<TreeNode> childTree = new ArrayList<>();
        // nodeList:所有节点集合(所有数据)
        for (TreeNode treeNode : treeList) {
            // 判断当前节点的父节点ID是否等于根节点的ID,即当前节点为其下的子节点
            if (treeNode.getParentId().equals(pNode.getId())) {
                // 再递归进行判断当前节点的情况,调用自身方法
                childTree.add(buildChildTree(treeNode,treeList));
            }
        }
        // for循环结束,即节点下没有任何节点,树形构建结束,设置树结果
        pNode.setChildren(childTree);
        return pNode;
    }
}

结果打印

[
    {
        "children": [
            {
                "children": [
                    {
                        "children": [],
                        "id": "6",
                        "key": "6",
                        "parentId": "3",
                        "title": "父节点的ID是3"
                    }
                ],
                "id": "3",
                "key": "3",
                "parentId": "1",
                "title": "父节点是A"
            }
        ],
        "id": "1",
        "key": "1",
        "parentId": "0",
        "title": "顶级节点A"
    },
    {
        "children": [
            {
                "children": [],
                "id": "4",
                "key": "4",
                "parentId": "2",
                "title": "父节点是B"
            },
            {
                "children": [],
                "id": "5",
                "key": "5",
                "parentId": "2",
                "title": "父节点是B"
            }
        ],
        "id": "2",
        "key": "2",
        "parentId": "0",
        "title": "顶级节点B"
    }
]

构建树工具类

public class TreeUtils {

    private TreeUtils() {
    }

    /**
     * @param collection  集合
     * @param getId       子节点
     * @param getParentId 父节点
     * @param setNode     设置子集合
     * @param <E>         集合的类型
     * @param <R>         节点属性的类型
     * @return Collection
     */
    public static <E, R> Collection<E> tree(Collection<E> collection, Function<E, R> getId, Function<E, R> getParentId, BiConsumer<E, Collection<E>> setNode) {
        Collection<E> root = new LinkedList<>();
        for (E node : collection) {
            R parentId = getParentId.apply(node);
            R id = getId.apply(node);
            Collection<E> elements = new LinkedList<>();
            boolean isParent = true;
            for (E element : collection) {
                if (id.equals(getParentId.apply(element))) {
                    elements.add(element);
                }
                if (isParent && getId.apply(element).equals(parentId)) {
                    isParent = false;
                }
            }
            if (isParent) {
                root.add(node);
            }
            setNode.accept(node, elements);
        }
        return root;
    }

    /**
     * 递归构造树
     *
     * @param allList     全部数据
     * @param getId       子节点
     * @param getParentId 父节点
     * @param vertexId    顶点ID 可为空
     * @param setNode     设置子集合
     * @param <E>         集合的类型
     * @param <R>         节点属性的类型
     * @return List
     */
    private static <E, R> List<E> constructionTree(List<E> allList, Function<E, R> getId, Function<E, R> getParentId, R vertexId, BiConsumer<E, List<E>> setNode) {
        List<E> rootNodeList = new ArrayList<>();
        List<E> treeList = new ArrayList<>();
        allList.forEach(x -> {
            if (Objects.equals(vertexId, getParentId.apply(x))) {
                rootNodeList.add(x);
            }
        });
        for (E treeRootNode : rootNodeList) {
            buildChildTree(treeRootNode, allList, getId, getParentId, setNode);
            treeList.add(treeRootNode);
        }
        return treeList;
    }

    /**
     * 递归构造树(过滤)
     */
    private static <E, R> List<E> constructionTree(List<E> list, Function<E, R> getId, Function<E, R> getParentId,
                                                   R vertexId, BiConsumer<E, List<E>> setNode, Function<E, E> filter) {
        List<E> allList = new ArrayList<>();
        List<E> rootNodeList = new ArrayList<>();
        List<E> treeList = new ArrayList<>();
        list.forEach(x -> {
            E apply = filter.apply(x);
            allList.add(apply);
            if (Objects.equals(vertexId, getParentId.apply(x))) {
                rootNodeList.add(apply);
            }
        });

        for (E treeRootNode : rootNodeList) {
            buildChildTree(treeRootNode, allList, getId, getParentId, setNode);
            treeList.add(treeRootNode);
        }
        return treeList;
    }

    /**
     * 递归-----构建子树形结构
     *
     * @param pNode    根节点(顶级节点)
     * @param treeList 全部数据
     * @return 整棵树
     */
    public static <E, R> E buildChildTree(E pNode, List<E> treeList, Function<E, R> getId, Function<E, R> getParentId, BiConsumer<E, List<E>> setNode) {
        List<E> childTree = new ArrayList<>();
        for (E treeNode : treeList) {
            if (getId.apply(pNode).equals(getParentId.apply(treeNode))) {
                childTree.add(buildChildTree(treeNode, treeList, getId, getParentId, setNode));
            }
        }
        setNode.accept(pNode, childTree);
        return pNode;
    }


    /**
     * 测试
     */
    public static void main(String[] args) {
        // 普通树1
        List<MenuItem> menuList = Arrays.asList(
                new MenuItem(1, "二级菜单1", 0),
                new MenuItem(2, "二级菜单1", null),
                new MenuItem(3, "二级菜单1", 2),
                new MenuItem(4, "二级菜单2", 2),
                new MenuItem(5, "三级菜单1", 3),
                new MenuItem(6, "三级菜单2", 3)
        );
        // 普通树2
        List<MenuItem> menuList2 = Arrays.asList(
                new MenuItem(1, "二级菜单1", 0),
                new MenuItem(2, "二级菜单1", null),
                new MenuItem(3, "二级菜单1", 1),
                new MenuItem(4, "二级菜单2", 2),
                new MenuItem(5, "三级菜单1", 3),
                new MenuItem(6, "三级菜单1", 3),
                new MenuItem(7, "三级菜单2", 4)
        );
        // 过滤
        List<DiyData> diyData = Arrays.asList(
                new DiyData(1, "二级菜单1", 0),
                new DiyData(2, "二级菜单1", null),
                new DiyData(3, "二级菜单1", 1),
                new DiyData(4, "二级菜单2", 2),
                new DiyData(5, "三级菜单1", 3),
                new DiyData(6, "三级菜单1", 3),
                new DiyData(7, "三级菜单2", 4)
        );
        // 不过滤
        List<DiyData> diyData2 = Arrays.asList(
                new DiyData(1, "二级菜单1", 0),
                new DiyData(2, "二级菜单1", null),
                new DiyData(3, "二级菜单1", 1),
                new DiyData(4, "二级菜单2", 2),
                new DiyData(5, "三级菜单1", 3),
                new DiyData(6, "三级菜单1", 3),
                new DiyData(7, "三级菜单2", 4)
        );

        // 普通树1
        Collection<MenuItem> tree = TreeUtils.tree(menuList, MenuItem::getId, MenuItem::getParentId, (x, y) -> x.setChildren((List<MenuItem>) y));
        System.out.println(tree);

        // 普通树2
        List<MenuItem> tree2 = TreeUtils.constructionTree(menuList2, MenuItem::getId, MenuItem::getParentId, null, MenuItem::setChildren);
        System.out.println(tree2);

        // 过滤
        List<DiyData> tree3 = TreeUtils.constructionTree(diyData, DiyData::getId, DiyData::getParentId, null, DiyData::setChildren
                , x -> (DiyData) new DiyData()
                        .setKey(x.getId())
                        .setId(x.getId())
                        .setTitle(x.getName())
                        .setParentId(x.getParentId()));
        System.out.println(tree3);

        // 不过滤
        List<DiyData> tree4 = TreeUtils.constructionTree(diyData2, DiyData::getId, DiyData::getParentId, null, DiyData::setChildren);
        System.out.println(tree4);
    }

    /**
     * 测试普通 实体类
     */
    @Data
    @Accessors(chain = true)
    @AllArgsConstructor
    @NoArgsConstructor
    static public class MenuItem {
        private Integer id;
        private String name;
        private Integer parentId;
        private List<MenuItem> children;

        public MenuItem(Integer id, String name, Integer parentId) {
            this.id = id;
            this.name = name;
            this.parentId = parentId;
        }
    }

    /**
     * 测试过滤 实体类
     */
    @Data
    @EqualsAndHashCode(callSuper = true)
    @ToString(callSuper = true)
    @Accessors(chain = true)
    @AllArgsConstructor
    @NoArgsConstructor
    static public class DiyData extends TreeNode {
        /**
         * id 需映射 TreeNode 中的 key
         */
        private Integer id;
        /**
         * id 需映射 TreeNode 中的 title
         */
        private String name;
        private Integer parentId;
    }


    /**
     * 供前端组件使用,可自行修改
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Accessors(chain = true)
    public static class TreeNode {

        /**
         * 前端需要对应的id
         */
        private Integer key;

        /**
         * 节点id
         */
        private Integer id;

        /**
         * 父节点id
         */
        private Integer parentId;

        /**
         * 前端需要对应公司的name
         */
        private String title;

        /**
         * 子节点
         */
        private List<? extends TreeNode> children;
    }
}

测试

[TreeUtils.MenuItem(id=1, name=二级菜单1, parentId=0, children=[]), TreeUtils.MenuItem(id=2, name=二级菜单1, parentId=null, children=[TreeUtils.MenuItem(id=3, name=二级菜单1, parentId=2, children=[TreeUtils.MenuItem(id=5, name=三级菜单1, parentId=3, children=[]), TreeUtils.MenuItem(id=6, name=三级菜单2, parentId=3, children=[])]), TreeUtils.MenuItem(id=4, name=二级菜单2, parentId=2, children=[])])]
[TreeUtils.MenuItem(id=2, name=二级菜单1, parentId=null, children=[TreeUtils.MenuItem(id=4, name=二级菜单2, parentId=2, children=[TreeUtils.MenuItem(id=7, name=三级菜单2, parentId=4, children=[])])])]
[TreeUtils.DiyData(super=TreeUtils.TreeNode(key=2, id=2, parentId=null, title=二级菜单1, children=[TreeUtils.DiyData(super=TreeUtils.TreeNode(key=4, id=4, parentId=2, title=二级菜单2, children=[TreeUtils.DiyData(super=TreeUtils.TreeNode(key=7, id=7, parentId=4, title=三级菜单2, children=[]), id=7, name=null, parentId=4)]), id=4, name=null, parentId=2)]), id=2, name=null, parentId=null)]
[TreeUtils.DiyData(super=TreeUtils.TreeNode(key=null, id=2, parentId=null, title=null, children=[TreeUtils.DiyData(super=TreeUtils.TreeNode(key=null, id=4, parentId=2, title=null, children=[TreeUtils.DiyData(super=TreeUtils.TreeNode(key=null, id=7, parentId=4, title=null, children=[]), id=7, name=三级菜单2, parentId=4)]), id=4, name=二级菜单2, parentId=2)]), id=2, name=二级菜单1, parentId=null)]

参考博文:java树形结构添加层级

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
$(function(){ $.fn.extend({ SimpleTree:function(options){ //初始化参数 var option = $.extend({ click:function(a){ } },options); option.tree=this; /* 在参数对象中添加对当前菜单树的引用,以便在对象中使用该菜单树 */ option._init=function(){ /* * 初始化菜单展开状态,以及分叉节点的样式 */ this.tree.find("ul ul").hide(); /* 隐藏所有子级菜单 */ this.tree.find("ul ul").prev("li").removeClass("open"); /* 移除所有子级菜单父节点的 open 样式 */ this.tree.find("ul ul[show='true']").show(); /* 显示 show 属性为 true 的子级菜单 */ this.tree.find("ul ul[show='true']").prev("li").addClass("open"); /* 添加 show 属性为 true 的子级菜单父节点的 open 样式 */ }/* option._init() End */ /* 设置所有超链接不响应单击事件 */ this.find("a").click(function(){ $(this).parent("li").click(); return false; }); /* 菜单项 接受单击 */ this.find("li").click(function(){ /* * 当单击菜单项 * 1.触发用户自定义的单击事件,将该 标签中的第一个超链接做为参数传递过去 * 2.修改当前菜单项所属的子菜单的显示状态(如果等于 true 将其设置为 false,否则将其设置为 true) * 3.重新初始化菜单 */ option.click($(this).find("a")[0]); /* 触发单击 */ /* * 如果当前节点下面包含子菜单,并且其 show 属性的值为 true,则修改其 show 属性为 false * 否则修改其 show 属性为 true */ /* if($(this).next("ul").attr("show")=="true"){ $(this).next("ul").attr("show","false"); }else{ $(this).next("ul").attr("show","true"); }*/ /* 初始化菜单 */ option._init(); }); /* 设置所有父节点样式 */ this.find("ul").prev("li").addClass("folder"); /* 设置节点“是否包含子节点”属性 */ this.find("li").find("a").attr("hasChild",false); this.find("ul").prev("li").find("a").attr("hasChild",true); /* 初始化菜单 */ option._init(); }/* SimpleTree Function End */ }); });
以下是一个简单的 Java 工具类,可以将 List 转换为树形结构: ```java import java.util.*; public class TreeUtil { public static <T extends TreeNode> List<T> buildTree(List<T> nodes) { if (nodes == null || nodes.size() == 0) { return Collections.emptyList(); } Map<Long, T> nodeMap = new HashMap<>(); for (T node : nodes) { nodeMap.put(node.getId(), node); } List<T> rootNodes = new ArrayList<>(); for (T node : nodes) { T parent = nodeMap.get(node.getParentId()); if (parent != null) { parent.addChild(node); } else { rootNodes.add(node); } } return rootNodes; } public interface TreeNode { Long getId(); Long getParentId(); void addChild(TreeNode child); List<? extends TreeNode> getChildren(); } } ``` 这个工具类包含了一个通用的接口 `TreeNode`,通过实现这个接口,可以将任意类型的 List 转换为树形结构。 `TreeNode` 接口包含了三个方法: - `getId()`:获取节点的唯一标识符。 - `getParentId()`:获取节点的父节点标识符。 - `addChild(TreeNode child)`:将一个子节点添加到当前节点。 - `getChildren()`:获取当前节点的所有子节点。 使用这个工具类非常简单,只需要将需要转换的 List 传入 `buildTree()` 方法中即可: ```java List<MyNode> nodes = ...; // 获取需要转换的 List List<MyNode> rootNodes = TreeUtil.buildTree(nodes); ``` 其中 `MyNode` 是一个实现了 `TreeNode` 接口的自定义类。注意,为了能够正确地构建树形结构,每个节点的 `getParentId()` 方法必须返回其父节点的 `getId()` 值。否则,节点将无法正确地添加到树中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我认不到你

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值