实现通用动态层级树形结构

  • Post author:
  • Post category:其他


实现功能如题:根据给定数据来源,自动生成一颗层级可变的树,其节点类型使用泛型,利用反射在运行时动态获取节点信息,满足通用的需求。


注意:

本例中的节点需要带有parentid属性作为关联关系的标识符;需要childrenSet作为子节点的存储空间;需要id属性作为节点唯一标识符。

设计思路

要设计一颗动态树,我思考的点有以下几个:

1、确定根节点

2、给定父节点的子节点获取

3、对给定节点递归,下钻获取其所有子节点直至叶子节点层级

4、构造树形结构

具体实现

注意此处省略get、set方法以及日志方法,使用更为优雅的lombok工具类库来自动添加此类方法。

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONArray;
import org.springframework.util.Assert;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author yanzy
 * @description 构造树形结构 (id、parentid类型为Long)
 * @date 2018-04-10 15:09
 * @created by intelliJ IDEA
 */
@Data
@Slf4j
public class TreeBuilder<T> {
    protected List<T> objList;
    protected Set<T> rootSet = new HashSet<>(16);
    protected Set<T> leafSet = new HashSet<>(16);

    /**
     * 是否为可以构建树的类型
     * @return true:符合条件;false:不符合条件
     * @throws NoSuchFieldException
     */
    public boolean isBuildType() throws NoSuchFieldException {
        try {
            Assert.notNull(objList, "-------->init error:objList is null!");
            Assert.notEmpty(objList, "-------->init error:there is no element in objList");
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        //若id不为Long型、parentid
        if (objList.get(0).getClass().getDeclaredField("parentid") == null) {
            if (log.isDebugEnabled()) {
                log.debug("----->此类型不能构造树形结构:{}", objList.get(0).getClass().getName());
            }
            return false;
        } else {
            return true;
        }
    }

    /**
     * 获取根节点
     * @return Set<T> 根节点列表,获取失败返回null
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    private Set<T> getRoots() throws NoSuchFieldException, IllegalAccessException {
        if (!isBuildType()) {
            return null;
        }
        if (rootSet!=null) {
            this.rootSet.clear();
        }
        if(leafSet!=null){
            this.leafSet.clear();
        }
        if (this.objList.size() > 0 && this.objList != null) {
            for (T node : objList) {
                Field parentidFiled = node.getClass().getDeclaredField("parentid");
                parentidFiled.setAccessible(true);

                if(parentidFiled.get(node)==null){
                    this.rootSet.add(node);
                }else{
                    this.leafSet.add(node);
                }
            }
        }
        return rootSet;
    }

    /**
     * 获取子节点,并拼装到对应父节点中
     * @param parentNode 父节点对象
     * @return Set<T> 传入节点下子节点列表,获取失败返回null
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    public Set<T> getChildren(T parentNode) throws NoSuchFieldException, IllegalAccessException {
        Set<T> childrenSet = new HashSet<>(16);
        if (parentNode == null) {
            log.warn("------->parentNode is null when get children nodes!");
            return null;
        }
        for (T node : this.leafSet) {
            Field parentidFiled = node.getClass().getDeclaredField("parentid");
            parentidFiled.setAccessible(true);
            Field idFiled = parentNode.getClass().getDeclaredField("id");
            idFiled.setAccessible(true);
            if (parentidFiled.get(node).equals(idFiled.get(parentNode))) {
                childrenSet.add(node);
            }
        }
        return childrenSet;
    }

    /**
     * 递归子节点
     * @param node 递归起始点,若有子节点向下递归;若无子节点,退出递归
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    public void buildChildNode(T node) throws NoSuchFieldException, IllegalAccessException {
        Set<T> childrenSet = this.getChildren(node);
        if (!childrenSet.isEmpty()) {
            for (T childNode : childrenSet) {
                this.buildChildNode(childNode);
            }
        }
        //node.getClass().getField("childrenSet").setAccessible(true);
        node.getClass().getField("childrenSet").set(node, childrenSet);
    }

    /**
     * 构造树型结构
     * @return List<T> 属性结构列表
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    public List<T> buildTree() throws NoSuchFieldException, IllegalAccessException {
        List<T> treeList = new ArrayList<>(16);
        this.setRootSet(this.getRoots());
        for (T node : this.rootSet) {
            this.buildChildNode(node);
            treeList.add(node);
        }
        return treeList;
    }

    /**
     * 构造json树型结构
     * @return String 解析为json字符串的属性结构
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    public String buildJsonTree() throws NoSuchFieldException, IllegalAccessException {
        List<T> nodeTree = this.buildTree();
        JSONArray jsonArray = JSONArray.fromObject(nodeTree);
        return jsonArray.toString();
    }
}

单元测试及结果

构造一个:一个根节点、两个中间节点、4个叶子节点的测试数据源。

节点类型为:

import lombok.Data;
import java.io.Serializable;
import java.sql.Date;
import java.util.Set;


/**
 * @author yanzy
 * @description 资料智库-材料报告
 * @date 2018-04-10 14:17
 * @created by intelliJ IDEA
 */
/*
oracle字段 Hibernate映射类型 java类型
number big_decimal java.math.BigDecimal
number(1) boolean Boolean
number(2)2至4之间 byte Byte
number(8)4至8之间 integer Integer
numbernumber(10)8以上 long Long
 */
@Data
public class MaterialPo implements Serializable{
    Long id;
    String label;
    String type;
    String imgUrl;
    Date time;
    String documentUrl;
    Long parentid;
    Byte isleaf;
    public Set<MaterialPo> childrenSet;
}

单元测试类:

import dist.dgp.model.smr.pos.MaterialPo;
import dist.dgp.util.TreeBuilder;
import org.junit.Test;

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

/**
 * @author yanzy
 * @description
 * @date 2018-04-10 17:32
 * @created by intelliJ IDEA
 */
public class TreeBuilderTest {
    @Test
    public void testTreeBuilder() throws NoSuchFieldException, IllegalAccessException {
        //一个根节点,两个中间节点,四个叶子节点
        MaterialPo mRoot = new MaterialPo();
        MaterialPo mMiddle1 = new MaterialPo();
        MaterialPo mMiddle2 = new MaterialPo();
        MaterialPo mLeaf11 = new MaterialPo();
        MaterialPo mLeaf12 = new MaterialPo();
        MaterialPo mLeaf21 = new MaterialPo();
        MaterialPo mLeaf22 = new MaterialPo();

        mRoot.setId(Long.valueOf(1));
        mRoot.setIsleaf((byte) 0);

        mMiddle1.setId(Long.valueOf(2));
        mMiddle1.setIsleaf((byte)0);
        mMiddle1.setParentid(Long.valueOf(1));

        mMiddle2.setId(Long.valueOf(3));
        mMiddle2.setIsleaf((byte)0);
        mMiddle2.setParentid(Long.valueOf(1));

        mLeaf11.setId(Long.valueOf(4));
        mLeaf11.setIsleaf((byte)1);
        mLeaf11.setParentid(Long.valueOf(2));

        mLeaf12.setId(Long.valueOf(5));
        mLeaf12.setIsleaf((byte)1);
        mLeaf12.setParentid(Long.valueOf(2));

        mLeaf21.setId(Long.valueOf(6));
        mLeaf21.setIsleaf((byte)1);
        mLeaf21.setParentid(Long.valueOf(3));

        mLeaf22.setId(Long.valueOf(7));
        mLeaf22.setIsleaf((byte)1);
        mLeaf22.setParentid(Long.valueOf(3));

        List<MaterialPo> objList = new ArrayList<MaterialPo>(16);
        objList.add(mRoot);
        objList.add(mMiddle1);
        objList.add(mMiddle2);
        objList.add(mLeaf11);
        objList.add(mLeaf12);
        objList.add(mLeaf21);
        objList.add(mLeaf22);


        /*********************正式开始测试树型构造****************************/
        TreeBuilder<MaterialPo> treeBuilder = new TreeBuilder<>();
        treeBuilder.setObjList(objList);
        System.out.println(treeBuilder.buildJsonTree());
    }
}

最终结果如下


[{"childrenSet":[{"childrenSet":[{"childrenSet":[],"documentUrl":"","id":5,"imgUrl":"","isleaf":1,"label":"","parentid":2,"time":null,"type":""},{"childrenSet":[],"documentUrl":"","id":4,"imgUrl":"","isleaf":1,"label":"","parentid":2,"time":null,"type":""}],"documentUrl":"","id":2,"imgUrl":"","isleaf":0,"label":"","parentid":1,"time":null,"type":""},{"childrenSet":[{"childrenSet":[],"documentUrl":"","id":7,"imgUrl":"","isleaf":1,"label":"","parentid":3,"time":null,"type":""},{"childrenSet":[],"documentUrl":"","id":6,"imgUrl":"","isleaf":1,"label":"","parentid":3,"time":null,"type":""}],"documentUrl":"","id":3,"imgUrl":"","isleaf":0,"label":"","parentid":1,"time":null,"type":""}],"documentUrl":"","id":1,"imgUrl":"","isleaf":0,"label":"","parentid":0,"time":null,"type":""}]


可自行格式化观看。



版权声明:本文为yzy199391原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。