美文网首页
【Java工具类】List转Tree通用工具类

【Java工具类】List转Tree通用工具类

作者: 灰色孤星 | 来源:发表于2020-05-05 14:59 被阅读0次

    一、背景

    最近的开发工作用到“树”模型比较多,例如节点树、权限树等。
    每一个实体都有自己特殊的字段,所以最初的解决方法是给每一个实体写独特的转换方法。
    然后需要转换为树的实体变多,代码就会冗余,因此写一个工具类,提供对应的方法将常见的List转换为Tree。

    二、实现

    1.效果

    UI框架:layui(官方文档:https://www.layui.com/doc/
    在使用layui的过程中发现,目前layui的组件功能并没有很强大,比如树组件就不能实现“只选单个”,但是layui简单易用,源码也不复杂,因此可以通过修改源码来达到目的。
    后续计划将自己在使用layui过程中遇到的问题和解决方法写出来分享,欢迎各位码友关注学习。

    简单的节点树.png

    2.编码实现

    2.1 实体TreeDot

    前端框架实现树组件,一般都有自己独特的数据源格式。从layui文档可看到layui实现树组件的数据源格式:


    layui树形组件数据源格式.png

    因此,首先建一个实体TreeDot,那么将这个实体返回前端就可以直接渲染成树。使用其他的UI框架类推。

    package pri.xiaowd.layui.pojo;
    
    import lombok.Data;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    /***
     * 树点
     * 需要实现树的类可以继承该类,手写set方法,在设定本身属性值时同时设置该类中的相关属性
     * @ClassName TreeTreeDot
     * @Author xiaowd
     * @DateTime 2020/2/23 15:58
     */
    @Data
    public class TreeDot<T> {
    
        /**
         * 点ID
         */
        private String id;
        /**
         * 点名称
         */
        private String title;
        /**
         * 父点ID
         */
        private String parentId;
        /**
         * 该点是否展开,默认不展开
         */
        private Boolean spread = false;
        /**
         * 该点是否选中,默认不选中
         */
        private Boolean checked = false;
        /**
         * 该点的图标,默认不设置
         */
        private String icon;
        /**
         * 该点的其他属性
         */
        private Map<String,Object> attributes = new HashMap<>();
        /**
         * 该点的子树集合
         */
        private List<TreeDot<T>> children = new ArrayList<>();
    
    }
    

    TreeDot类的字段和文档提供的字段可能不完全一样,这个根据自己的实际需求来定就行。最为重要的是 id、title、children 三个字段,layui根据这三个字段就可以构建一颗树,其他都可以不要,也可以新增自己额外需要的,例如 parentId、attributes ,新增的字段不会影响树的构建。
    另外,这里用到了泛型,因为需要适应所有需要转换为树的实体。

    2.2 实体Node

    建自己需要转换成树的原始实体,一般就是从数据源读取的数据。这个实体需要继承上面建的TreeDot,并重写set方法。这里以Node为例:

    package pri.xiaowd.layui.pojo;
    
    import lombok.Data;
    
    /***
     * 节点
     * @ClassName Node
     * @Author xiaowd
     * @DateTime 2020/1/31 15:37
     */
    @Data
    public class Node extends TreeDot {
    
        /**
         * 节点编码
         */
        private Integer nodeId;
        /**
         * 节点名称
         */
        private String nodeName;
        /**
         * 父节点编码
         */
        private Integer parentNodeId;
        /**
         * 创建时间
         */
        private Long time;
    
        public void setNodeId(Integer nodeId) {
            this.nodeId = nodeId;
            super.setId(String.valueOf(nodeId));
        }
    
        public void setNodeName(String nodeName) {
            this.nodeName = nodeName;
            super.setTitle(nodeName);
        }
    
        public void setParentNodeId(Integer parentNodeId) {
            this.parentNodeId = parentNodeId;
            super.setParentId(String.valueOf(parentNodeId));
        }
    
        public void setTime(Long time) {
            this.time = time;
            super.getAttributes().put("time",time);
        }
    
    }
    

    继承TreeDot之后,也就拥有了TreeDot的字段,因此重写set方法,将字段的值同时赋给TreeDot的字段。这里有一个小技巧,因为必要的字段是id和title,那么多余的字段就可以统一放在一个集合中attributes。注意,attributes需要一开始就实例化,不然会报错哦。

    2.3 工具类TreeDotUtils

    接下来就是具体的工具类了,这个工具类是通用的,因此需要使用泛型(同时使用了递归,就这两个重要的知识点),只要是继承了TreeDot并重写set方法的实体都可以转化:

    package pri.xiaowd.layui.util;
    
    import pri.xiaowd.layui.pojo.TreeDot;
    
    import java.util.ArrayList;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Set;
    
    /***
     * 操作“树”的工具
     * @ClassName TreeDotUtils
     * @Author xiaowd
     * @DateTime 2020/4/22 9:14
     */
    public class TreeDotUtils {
    
        /**
         * 将List转换为Tree
         * @MethosName convertListToTreeDot
         * @Author xiaowd
         * @Date 2020/4/22 9:17
         * @param tList
         * @return java.util.List<cn.eshore.common.entity.Tree<T>>
         */
        public static <T extends TreeDot> List<TreeDot<T>> convertListToTreeDot(List<T> tList){
            List<TreeDot<T>> treeDotList = new ArrayList<>();
            if(tList != null && tList.size() > 0){
                for(T t:tList){
                    if(!isTreeDotExist(tList,t.getParentId())){
                        //不存在以父ID为ID的点,说明是当前点是顶级节点
                        TreeDot<T> tTreeDot = getTreeDotByT(t, tList);
                        treeDotList.add(tTreeDot);
                    }
                }
            }
            return treeDotList;
        }
    
        /**
         * 根据ID判断该点是否存在
         * @MethosName isTreeDotExist
         * @Author xiaowd
         * @Date 2020/4/22 9:50
         * @param tList
         * @param id 点ID
         * @return java.lang.Boolean
         */
        private static <T extends TreeDot> Boolean isTreeDotExist(List<T> tList, String id) {
            for(T t:tList){
                if(t.getId().equals(id)){
                    return true;
                }
            }
            return false;
        }
    
        /**
         * 获取指定父点的子树
         * @MethosName getChildTreeList
         * @Author xiaowd
         * @Date 2020/4/22 10:02
         * @param parentTreeDot 父点
         * @param tList
         * @return java.util.List<cn.eshore.common.entity.Tree<T>>
         */
        private static <T extends TreeDot> List<TreeDot<T>> getChildTreeDotList(TreeDot<T> parentTreeDot, List<T> tList){
            List<TreeDot<T>> childTreeDotList = new ArrayList<>();
            for(T t:tList){
                if(parentTreeDot.getId().equals(t.getParentId())){
                    //如果父ID是传递树点的ID,那么就是传递树点的子点
                    TreeDot<T> tTreeDot = getTreeDotByT(t,tList);
                    childTreeDotList.add(tTreeDot);
                }
            }
            return childTreeDotList;
        }
    
        /**
         * 根据实体获取TreeDot
         * @MethosName getTreeDotByT
         * @Author xiaowd
         * @Date 2020/5/4 22:17
         * @param t
         * @param tList
         * @return pri.xiaowd.layui.pojo.TreeDot<T>
         */
        private static <T extends TreeDot> TreeDot<T> getTreeDotByT(T t,List<T> tList){
            TreeDot<T> tTreeDot = new TreeDot<>();
            tTreeDot.setId(t.getId());
            tTreeDot.setParentId(t.getParentId());
            tTreeDot.setTitle(t.getTitle());
            tTreeDot.setChecked(t.getChecked());
            tTreeDot.setIcon(t.getIcon());
            tTreeDot.setAttributes(t.getAttributes());
            tTreeDot.setChildren(getChildTreeDotList(tTreeDot,tList));
            return tTreeDot;
        }
    
        /**
         * 获取根据指定ID所在点为父点的树
         * @MethosName getTreeDotById
         * @Author xiaowd
         * @Date 2020/4/22 15:00
         * @param id
         * @param treeDotList
         * @return cn.eshore.common.entity.TreeDot<T>
         */
        public static <T extends TreeDot> TreeDot<T> getTreeDotById(String id,List<TreeDot<T>> treeDotList){
            if(id != null && !"".equals(id) && treeDotList != null && treeDotList.size() > 0){
                for(TreeDot<T> treeDot:treeDotList){
                    if(id.equalsIgnoreCase(treeDot.getId())){
                        return treeDot;
                    }
                    if(treeDot.getChildren() != null && treeDot.getChildren().size() > 0){
                        TreeDot<T> td = getTreeDotById(id, treeDot.getChildren());
                        if(td != null){
                            return td;
                        }
                    }
                }
            }
            return null;
        }
    
        /**
         * 将TreeList的所有点转换为ID的Set集合
         * @MethosName convertTreeDotToIdSet
         * @Author xiaowd
         * @Date 2020/4/22 16:13
         * @param treeDotList
         * @param kClass ID的类型
         * @return java.util.Set<K>
         */
        public static <T extends TreeDot,K> Set<K> convertTreeDotToIdSet(List<TreeDot<T>> treeDotList,Class<K> kClass){
            Set<K> idSet = new HashSet<>();
            if(treeDotList != null && treeDotList.size() > 0){
                for(TreeDot<T> treeDot:treeDotList){
                    idSet.add((K)treeDot.getId());
                    if(treeDot.getChildren() != null && treeDot.getChildren().size() > 0){
                        idSet.addAll(convertTreeDotToIdSet(treeDot.getChildren(),kClass));
                    }
                }
            }
            return idSet;
        }
    
        /**
         * 将Tree(单点)的所有点转换为ID的Set集合
         * @MethosName convertTreeDotToIdSet
         * @Author xiaowd
         * @Date 2020/4/29 9:08
         * @param treeDot
         * @param kClass
         * @return java.util.Set<K>
         */
        public static <T extends TreeDot,K> Set<K> convertTreeDotToIdSet(TreeDot<T> treeDot,Class<K> kClass){
            Set<K> idSet = new HashSet<>();
            if(treeDot != null){
                idSet.add((K)treeDot.getId());
                if(treeDot.getChildren() != null && treeDot.getChildren().size() > 0){
                    idSet.addAll(convertTreeDotToIdSet(treeDot.getChildren(),kClass));
                }
            }
            return idSet;
        }
    
    }
    

    在需要转化时,只需要调用 convertListToTreeDot(List<T> tList) 方法即可。参数List<T>是原始的实体集合。

    • 该方法在拿到原始的实体集合List<T>后,进行遍历,通过将遍历得到的实体的父ID传入 isTreeDotExist(List<T> tList, String id) 方法判断该实体的实体是否存在,如果不存在说明就是根实体(PS.根实体也是一个集合,根不一定就只有一个);
    • 找到根实体之后,通过 getTreeDotByT(T t,List<T> tList) 方法将该实体转换为树点TreeDot对象;该TreeDot对象中的children字段是子树集合,通过 getChildTreeDotList(TreeDot<T> parentTreeDot, List<T> tList) 获得;
    • getChildTreeDotList方法使用了递归。有两个参数,第一个是树点TreeDot<T>,要获取哪个树点的子树集合,这个参数就传哪个树点;第二个是原始的实体集合List<T>。
    • 遍历原始的实体集合List<T>,如果遍历得到的实体的父ID是传入的树点的ID,那么这个实体就是传入树点的子树集合其中一员,因此将该实体转换为树点并放到集合中,返回集合作为传入树点的子树集合。
    • 因为这个转换出来的树点也有自己的子树集合,那么就需要通过递归获取,只是第一个参数已经变成了这个转换出来的树点。
    • 工具类中还额外提供了集合方法:
      getTreeDotById(String id,List<TreeDot<T>> treeDotList) : 根据ID获取树点;
      convertTreeDotToIdSet(List<TreeDot<T>> treeDotList,Class<K> kClass) : 根据树集合(根有一到多个)获取其ID的Set集合;
      convertTreeDotToIdSet(TreeDot<T> treeDot,Class<K> kClass) : 根据树(根只有一个)获取其ID的Set集合。

    2.4 测试

    测试的话就不从数据库拿到List<T>了,手动建即可。

    • controller
        @RequestMapping("/tree")
        @ResponseBody
        public Map<String,Object> tree(){
            //国节点 中国
            Node rootNode = new Node();
            rootNode.setNodeId(110000);
            rootNode.setNodeName("中国");
            rootNode.setParentNodeId(0);
            rootNode.setTime(System.currentTimeMillis());
    
            //省节点 广东
            Node pNode1 = new Node();
            pNode1.setNodeId(120000);
            pNode1.setNodeName("广东");
            pNode1.setParentNodeId(110000);
            pNode1.setTime(System.currentTimeMillis());
    
            //市节点 广州
            Node cNode1 = new Node();
            cNode1.setNodeId(120001);
            cNode1.setNodeName("广州");
            cNode1.setParentNodeId(120000);
            cNode1.setTime(System.currentTimeMillis());
    
            //区节点 广州
            Node aNode1 = new Node();
            aNode1.setNodeId(1200011);
            aNode1.setNodeName("天河区");
            aNode1.setParentNodeId(120001);
            aNode1.setTime(System.currentTimeMillis());
    
            //子节点 湖南
            Node pNode2 = new Node();
            pNode2.setNodeId(130000);
            pNode2.setNodeName("湖南");
            pNode2.setParentNodeId(110000);
            pNode2.setTime(System.currentTimeMillis());
    
            //市节点 长沙
            Node cNode2 = new Node();
            cNode2.setNodeId(130001);
            cNode2.setNodeName("长沙");
            cNode2.setParentNodeId(130000);
            cNode2.setTime(System.currentTimeMillis());
    
            //子节点 上海
            Node pNode3 = new Node();
            pNode3.setNodeId(140000);
            pNode3.setNodeName("上海");
            pNode3.setParentNodeId(110000);
            pNode3.setTime(System.currentTimeMillis());
    
            List<Node> nodeList = new ArrayList<>();
            nodeList.add(rootNode);
            nodeList.add(pNode1);
            nodeList.add(cNode1);
            nodeList.add(aNode1);
            nodeList.add(pNode2);
            nodeList.add(cNode2);
            nodeList.add(pNode3);
    
            //转换
            List<TreeDot<Node>> nodeTreeDotList = TreeDotUtils.convertListToTreeDot(nodeList);
    
            Map<String,Object> result = new HashMap<>();
            result.put("nodeTreeDotList",nodeTreeDotList);
    
            return result;
        }
    
    • js
    <script type="text/javascript" th:inline="javascript">
            var ctxPath=[[${#httpServletRequest.getContextPath()}]];
            layui.use(['tree'],function(){
                var tree = layui.tree;
                tree.render({
                    elem: '#node-tree',
                    id: 'node-tree',
                    data: getNodeTreeDot(),
                    onlyIconControl: true,
                    edit: ['add'],
                    customOperate: true,
                    text: {
                        none: '无节点'
                    }
                });
            });
            
            function getNodeTreeDot() {
                var data = [];
                layui.jquery.ajax({
                    url: ctxPath + "/tree",
                    type: "post",
                    async: false,
                    success: function(result){
                        data = result.nodeTreeDotList;
                    }
                });
                console.log(data)
                return data;
            }
        </script>
    
    • 效果
    最终展示效果.png

    三、结语

    将List转换为树有不同的写法,但是基本的思路是一样的,将这种转换写成一个工具类,我认为对减少代码冗余、优化代码美观程度、提高编码效率都有较大的帮助。如果各位码友有自己的想法,或者觉得我的写法还可以再优化,欢迎给我留言,共同交流。
    交流邮箱:weidag_xiao@sina.com

    相关文章

      网友评论

          本文标题:【Java工具类】List转Tree通用工具类

          本文链接:https://www.haomeiwen.com/subject/wsqughtx.html