实现功能如题:根据给定数据来源,自动生成一颗层级可变的树,其节点类型使用泛型,利用反射在运行时动态获取节点信息,满足通用的需求。
注意:
本例中的节点需要带有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":""}]
可自行格式化观看。