一种不使用递归创建的层级树结构的泛型方法
1.背景
之前有个业务需求需要将一个原数据集合根据每个数据对象中的id,pid构造成树结构给前台展示。当时通过递归的方式完成了,现在想想递归创建层级树时间和空间的消耗比较大,并且不知道层级很有可能栈溢出,所以重新想了一种方法来完成层级树的构造。
2.代码
package com.avro.util;
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Lists;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
/**
* 不使用递归创建树结构v 1.0
*/
@Slf4j
public class GenericTreeBuildUtils {
private GenericTreeBuildUtils() {
}
private static final String ROOT_SIGN = "0";
/**
* 构造树结构
*
* @param sourceList 原数据集合
* @param idFieldName 原数据对象中的id字段名称
* @param pidFieldName 原数据对象中的pid字段名称
* @param childFieldName 原数据对象中的子节点集合字段名称
* @param <T> 原数据对象的类型
* @return T 构建好的树
* @throws NoSuchFieldException 反射无字段的异常
* @throws IllegalAccessException 反射获取参数值的异常
* @throws InvocationTargetException 反射调用方法的异常
*/
public static <T> T buildTree(List<T> sourceList, String idFieldName, String pidFieldName, String childFieldName)
throws NoSuchFieldException, IllegalAccessException, InvocationTargetException {
Map<String, List<T>> treeNodeMap = buildTreeMap(sourceList, pidFieldName);
return buildTreeStructure(sourceList, treeNodeMap, idFieldName, pidFieldName, childFieldName);
}
/**
* 将原数据转成pid和子节点集合一一对应的map结构
* @param sourceList 原数据集合
* @param idFieldName 原数据对象中的id字段名称
* @param pidFieldName 原数据对象中的pid字段名称
* @param childFieldName 原数据对象中的子节点集合字段名称
* @param <T> 原数据对象的类型
* @return T 构建好的树
* @throws NoSuchFieldException 反射无字段的异常
* @throws IllegalAccessException 反射获取参数值的异常
* @throws InvocationTargetException 反射调用方法的异常
*/
private static <T> T buildTreeStructure(List<T> sourceList, Map<String, List<T>> treeNodeMap, String idFieldName, String pidFieldName, String childFieldName)
throws NoSuchFieldException, IllegalAccessException, InvocationTargetException {
T rootTreeNode = null;
for (T treeNode : sourceList) {
// 反射获取id,pid
Class<?> nodeClass = treeNode.getClass();
String id = nodeClass.getDeclaredField(idFieldName).get(treeNode).toString();
String pid = nodeClass.getDeclaredField(pidFieldName).get(treeNode).toString();
// 判断是否根节点
if (ROOT_SIGN.equals(pid)) {
if (rootTreeNode != null) {
throw new IllegalAccessException("数据有误,重复的根节点");
}
rootTreeNode = treeNode;
}
// 通过反射调用设置子节点的方法
Method[] methods = nodeClass.getDeclaredMethods();
Method method = Arrays.stream(methods).filter(m -> {
char[] charArray = childFieldName.toCharArray();
charArray[0]-=32;
return m.getName().equals("set" + String.valueOf(charArray));
}).findAny().orElseThrow(() -> new IllegalAccessException("数据传入有误,传入的子节点集合字段名不存在"));
List<?> list = treeNodeMap.get(id) == null ? new ArrayList<>() : treeNodeMap.get(id);
method.invoke(treeNode, list);
}
return rootTreeNode;
}
/**
* 将原数据转成pid和子节点一一对应的map结构
*
* @param sourceList 原数据集合
* @param pidFieldName 原数据对象中的pid字段名称
* @param <T> 原数据对象的类型
* @return Map<String, List<T>> pid和子节点集合一一对应的map结构
* @throws NoSuchFieldException 反射无字段的异常
* @throws IllegalAccessException 反射获取参数值的异常
*/
private static <T> Map<String, List<T>> buildTreeMap(List<T> sourceList, String pidFieldName) throws NoSuchFieldException, IllegalAccessException {
// 初始化treeNodeMap大小
Map<String, List<T>> treeNodeMap = new HashMap<>(Math.max(sourceList.size() / 2, 16));
for (T treeNode : sourceList) {
// 通过反射获取pid
Class<?> nodeClass = treeNode.getClass();
String pid = nodeClass.getDeclaredField(pidFieldName).get(treeNode).toString();
// 通过treeNodeMap将pid对应的子节点挂到对应pid的节点上
List<T> childrenNodeList = treeNodeMap.get(pid);
if (childrenNodeList == null) {
childrenNodeList = new ArrayList<>();
childrenNodeList.add(treeNode);
treeNodeMap.put(pid, childrenNodeList);
} else {
childrenNodeList.add(treeNode);
}
}
return treeNodeMap;
}
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, InvocationTargetException {
DeviceInfo deviceInfo1 = DeviceInfo.builder().uniqueId("1").parentId("0").build();
DeviceInfo deviceInfo11 = DeviceInfo.builder().uniqueId("11").parentId("1").build();
DeviceInfo deviceInfo12 = DeviceInfo.builder().uniqueId("12").parentId("1").build();
DeviceInfo deviceInfo121 = DeviceInfo.builder().uniqueId("121").parentId("12").build();
List<DeviceInfo> list = Lists.newArrayList(deviceInfo1, deviceInfo11, deviceInfo12, deviceInfo121);
DeviceInfo treeNode = buildTree(list, "uniqueId", "parentId", "childrenList");
System.out.println(JSON.toJSONString(treeNode));
}
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class DeviceInfo {
public String uniqueId;
public String parentId;
public String address;
public List<DeviceInfo> childrenList;
}
3.测试结果
运行测试main方法,如下,构建成功: