一、需求
因为在做项目的过程中,需要把图1-1的execl表格的表头数据转成多叉树结构数据。其中图中execl表头的数据的排列方式完全严格按多叉树的结构进行排列的,项目需求的目标就是把这种多叉树的表头解析成java中的多叉树结构并形成json字符串返回给前端。
图1-1
二、代码实现
步骤一:poi工具解析execl表格数据,并把解析的数据存储到数据结构中,如图2-1所示
图2-1
简单的说,我们就是要把我们解析的execl中的数据,形成表2-2这种有层级关联的数据,其中最顶级的结点的父结点的parentid为null。
id(主键id) | parentid(父节点id) |
1 | null |
2 | 1 |
3 | 1 |
4 | 1 |
5 | 1 |
6 | 1 |
7 | 2 |
8 | 2 |
9 | 3 |
10 | 4 |
11 | 7 |
12 | 5 |
13 | 10 |
14 | 8 |
15 | 11 |
16 | 12 |
17 | 13 |
表2-2
这里,如何把数据存储成这样的数据存储到数据库中去我就不过多的描述,只要有一定java基础的程序员应该都会,下面我们描述根据这样的数据形成多叉树
结构的json数据返回给前端。
步骤二:
好了,开门见山,先上代码。代码中注释非常清晰,几乎都有相关的注释,所以以下代码不做过多的解析。
package org.jeecg.modules.powergrid.templatemanagement.common;
import com.alibaba.fastjson.JSON;
import org.jeecg.modules.powergrid.templatemanagement.common.interfaces.ITree;
import javax.validation.constraints.NotNull;
import java.lang.reflect.Field;
import java.util.*;
public class TreeUtils {
/**
* 集合转树结构
* @param collection 目标集合
* @param clazz 集合元素类型
* @param <T>
* @return
*/
public static <T> Collection<T> toTree(@NotNull Collection<T> collection,@NotNull Class<T> clazz){
return toTree(collection,null,null,null,clazz);
}
/**
* 集合转树结构,注意,使用此方法,则集合元素必须继承ITree接口
* @param collection 目标集合
* @param <T>
* @return
*/
public static <T extends ITree> Collection<T> toTree(@NotNull Collection<T> collection){
try {
if (collection==null || collection.isEmpty()) return null;// 如果目标集合为空,直接返回一个空树
// 找出所有的根节点
Collection<T> roots = null;
if (collection.getClass().isAssignableFrom(Set.class)) roots = new HashSet<>();
else roots = new ArrayList<>();
for (T tree:collection){
Object o = ITree.class.getMethod("getParentLevel").invoke(tree);
if (o instanceof String){
if (StringUtil.isEmpty((String) o)){
roots.add(tree);
}
}else if (o == null){
roots.add(tree);
}
}
// 从目标集合移除所有的根节点
collection.removeAll(roots);
// 为根节点添加孩子节点
for (T tree:roots){
addChild(tree,collection);
}
return roots;
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 集合转树结构
* @param collection 目标集合
* @param id 被依赖字段名称
* @param parent 依赖字段名称
* @param children 子节点集合属性名称
* @param clazz 集合元素类型
* @param <T>
* @return
*/
public static <T> Collection<T> toTree(@NotNull Collection<T> collection,String id,String parent,String children,@NotNull Class<T> clazz){
try {
if (collection==null || collection.isEmpty()) return null;// 如果目标集合为空,直接返回一个空树
if (StringUtil.isEmpty(id)) id = "headid";// 如果被依赖字段名称为空则默认为id
if (StringUtil.isEmpty(parent)) parent = "parentLevel";// 如果依赖字段为空则默认为parent
if (StringUtil.isEmpty(children)) children = "children";// 如果子节点集合属性名称为空则默认为children
Collection<T> roots = null;// 初始化根节点集合
if (collection.getClass().isAssignableFrom(Set.class)) roots = new HashSet<>();// 如果目标节点是一个set集合,则初始化根节点集合为hashset
else roots = new ArrayList<>();// 否则初始化为Arraylist,
// 这里集合初始化只分2中,要么是hashset,要么ArrayList,因为这两种最常用,其他不常用的摒弃
Field idField = null;
try {
idField=clazz.getDeclaredField(id);// 获取依赖字段
}catch (NoSuchFieldException e1){
idField=clazz.getSuperclass().getDeclaredField(id);
}
Field parentField = null;
try {
parentField = clazz.getDeclaredField(parent);// 获取被依赖字段
}catch (NoSuchFieldException e1){
parentField = clazz.getSuperclass().getDeclaredField(parent);
}
Field childrenField = null;// 获取孩子字段
try {
childrenField=clazz.getDeclaredField(children);
}catch (NoSuchFieldException e1){
childrenField=clazz.getSuperclass().getDeclaredField(children);
}
// 设置为可访问
idField.setAccessible(true);
parentField.setAccessible(true);
childrenField.setAccessible(true);
// 找出所有的根节点
for (T c:collection){
Object o = parentField.get(c);
if (o instanceof String){
if (StringUtil.isEmpty((String) o)) {// 如果父节点为空则说明是根节点,添加到根节点集合
roots.add(c);
}
}else {
if (o==null){
roots.add(c);
}
}
}
// 从目标集合移除所有根节点
collection.removeAll(roots);
for (T c:roots){// 遍历根节点,依次添加子节点
addChild(c,collection,idField,parentField,childrenField);
}
// 关闭可访问
idField.setAccessible(false);
parentField.setAccessible(false);
childrenField.setAccessible(false);
return roots;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static <T extends ITree> void addChild(T tree,Collection<T> collection){
try {
Object id = ITree.class.getMethod("getHeadid").invoke(tree);
Collection<T> children = (Collection<T>) ITree.class.getMethod("getChildren").invoke(tree);
for (T cc:collection){
Object o = ITree.class.getMethod("getParentLevel").invoke(cc);
if (id.equals(o)){// 如果当前节点的被依赖值和目标节点的被依赖值相等,则说明,当前节点是目标节点的子节点
if (children==null) {// 如果目标节点的孩子集合为null,初始化目标节点的孩子集合
if (collection.getClass().isAssignableFrom(Set.class)){// 如果目标集合是一个set集合,则初始化目标节点的孩子节点集合为set
children = new HashSet<>();
}else children = new ArrayList<>();// 否则初始化为list
}
// 将当前节点添加到目标节点的孩子节点
children.add(cc);
// 重设目标节点的孩子节点集合,这里必须重设,因为如果目标节点的孩子节点是null的话,这样是没有地址的,就会造成数据丢失,所以必须重设,如果目标节点所在类的孩子节点初始化为一个空集合,而不是null,则可以不需要这一步,因为java一切皆指针
ITree.class.getMethod("setChildren", Collection.class).invoke(tree,children);
// 递归添加孩子节点
addChild(cc,collection);
}
}
} catch (Exception e){
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 为目标节点添加孩子节点,此方法为私有,不能为公开,否则类修改信息无法恢复,后面有公开方法,其专门为目标节点添加子节点
* @param c 目标节点
* @param collection 目标集合
* @param idField
* @param parentField
* @param childrenField
* @param <T>
* @throws IllegalAccessException
*/
private static <T> void addChild(@NotNull T c,@NotNull Collection<T> collection,@NotNull Field idField,@NotNull Field parentField,@NotNull Field childrenField) throws IllegalAccessException {
Object id = idField.get(c);// 获取目标节点的被依赖值
Collection<T> children = (Collection<T>) childrenField.get(c);// 获取目标节点的孩子列表
for (T cc:collection){// 遍历目标集合
Object o = parentField.get(cc);// 获取当前节点的依赖值
if (id.equals(o)){// 如果当前节点的被依赖值和目标节点的被依赖值相等,则说明,当前节点是目标节点的子节点
if (children==null) {// 如果目标节点的孩子集合为null,初始化目标节点的孩子集合
if (collection.getClass().isAssignableFrom(Set.class)){// 如果目标集合是一个set集合,则初始化目标节点的孩子节点集合为set
children = new HashSet<>();
}else children = new ArrayList<>();// 否则初始化为list
}
// 将当前节点添加到目标节点的孩子节点
children.add(cc);
// 重设目标节点的孩子节点集合,这里必须重设,因为如果目标节点的孩子节点是null的话,这样是没有地址的,就会造成数据丢失,所以必须重设,如果目标节点所在类的孩子节点初始化为一个空集合,而不是null,则可以不需要这一步,因为java一切皆指针
childrenField.set(c,children);
// 递归添加孩子节点
addChild(cc,collection,idField,parentField,childrenField);
}
}
// 特别说明:大家可以看到此递归没有明显出口,其出口就是是否当前节点的依赖值和目标节点的被依赖值一样,一样就递归,不一样进不了if,自然出递归
// 此工具类自我感觉是最简单的,最实用的工具类,我看网上许多人写的,都是云的雾的,本来也想借鉴,但是实在没一个能看的感觉思路清晰,没办法,自己动手造轮子
}
/**
* 为目标节点添加孩子
* @param c 目标节点
* @param collection 目标集合
* @param id 被依赖字段名
* @param parent 依赖字段名
* @param children 孩子节点字段名
* @param clazz 集合元素所在类别
* @param <T>
*/
public static <T> void addChild(@NotNull T c,@NotNull Collection<T> collection,String id,String parent,String children,@NotNull Class<T> clazz){
try {
if (collection==null || collection.isEmpty()) return ;// 如果目标集合为空,直接返回一个空树
if (StringUtil.isEmpty(id)) id = "headid";// 如果被依赖字段名称为空则默认为id
if (StringUtil.isEmpty(parent)) parent = "parentLevel";// 如果依赖字段为空则默认为parent
if (StringUtil.isEmpty(children)) children = "children";// 如果子节点集合属性名称为空则默认为children
Field idField = null;
try {
idField=clazz.getDeclaredField(id);// 获取依赖字段
}catch (NoSuchFieldException e1){
idField=clazz.getSuperclass().getDeclaredField(id);
}
Field parentField = null;
try {
parentField = clazz.getDeclaredField(parent);// 获取被依赖字段
}catch (NoSuchFieldException e1){
parentField = clazz.getSuperclass().getDeclaredField(parent);
}
Field childrenField = null;// 获取孩子字段
try {
childrenField=clazz.getDeclaredField(children);
}catch (NoSuchFieldException e1){
childrenField=clazz.getSuperclass().getDeclaredField(children);
}
// 设置为可访问
idField.setAccessible(true);
parentField.setAccessible(true);
childrenField.setAccessible(true);
addChild(c,collection,idField,parentField,childrenField);
// 关闭可访问
idField.setAccessible(false);
parentField.setAccessible(false);
childrenField.setAccessible(false);
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 为目标节点添加孩子
* @param c 目标节点
* @param collection 目标集合
* @param clazz 集合元素所在类型
* @param <T>
*/
public static <T> void addChild(@NotNull T c,@NotNull Collection<T> collection,@NotNull Class<T> clazz){
addChild(c,collection,null,null,null,clazz);
}
}
package org.jeecg.modules.powergrid.templatemanagement.common.interfaces;
import java.io.Serializable;
import java.util.Collection;
public interface ITree {
/**
* 获取被依赖节点
* @return
*/
Serializable getHeadid();
/**
* 设置被依赖节点
* @param id
*/
void setHeadid(Serializable id);
/**
* 获取依赖节点
* @return
*/
Serializable getParentLevel();
/**
* 设置依赖节点
* @param parent
*/
void setParentLevel(Serializable parent);
/**
* 获取孩子列表
* @return
*/
Collection<? extends ITree> getChildren();
/**
* 设置孩子列表
* @param children
*/
void setChildren(Collection<? extends ITree> children);
}
三、代码运行结果(返回的json数据)
如图3-1所示,返回的结果都在dataBasicHeaderList数据项中,可以看出返回的是多叉树数据结构,由于数据过多我就不全部展示了。