开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情
前言:
作为一个面向CRUD开发的工程师,我们经常会遇到那种层级结构的数据需要处理,比如省市区的那种层级结构,数据结构是一层一层的,但是在我们关系型数据库中,数据是一行一行的,并不是那种树结构来显示,所以我们需要将List结构处理成Tree结构,当前层的id一定是下一层的parentId,我们也有可能用其他属性名进行表示,比如code--parentCode。因为最近两个迭代都遇到这种list转tree结构,所以(摸鱼的时候)写了一个list转换为Tree的结构的工具类
一丶源码
@Data
public class TreeConfig {
private String idKey = "id";
private String parentIdKey = "parentId";
private String children = "children";
}
UtilityClass
public class TreeUtils {
public static <T, E> List<T> buildTree(List<T> listParam, E parentId, TreeConfig treeConfig) {
Map<Object, List<T>> listMap = listParam
.stream()
.filter(t -> Objects.nonNull(TreeUtils.getParentId(t, treeConfig))
&& Objects.nonNull(TreeUtils.getId(t, treeConfig)))
.collect(Collectors.groupingBy(x -> TreeUtils.getParentId(x, treeConfig)));
List<T> oneLevel = listMap.get(parentId);
if (CollUtil.isEmpty(oneLevel)) {
return Collections.emptyList();
}
TreeUtils.handleTree(oneLevel, treeConfig, listMap);
return oneLevel;
}
private static <T> void handleTree(List<T> children, TreeConfig treeConfig, Map<Object, List<T>> listMap) {
for (T t : children) {
Object id = TreeUtils.getId(t, treeConfig);
List<T> childrens = listMap.get(id);
if (CollUtil.isNotEmpty(childrens)) {
TreeUtils.setChildren(t, treeConfig, childrens);
TreeUtils.handleTree(childrens, treeConfig, listMap);
}
}
}
private static <T> Object getParentId(T t, TreeConfig treeConfig){
Field[] fields = ReflectUtil.getFields(t.getClass());
for (Field field : fields) {
ReflectUtil.setAccessible(field);
String name = field.getName();
String parentIdKey = treeConfig.getParentIdKey();
if (Objects.equals(name, parentIdKey)) {
ReflectUtil.getFieldsValue(field);
return ReflectUtil.getFieldValue(t, field);
}
}
throw new RuntimeException("[TreeUtils.getFields]TreeConfig Error");
}
private static <T> Object getId(T t, TreeConfig treeConfig) {
Field[] fields = ReflectUtil.getFields(t.getClass());
for (Field field : fields) {
ReflectUtil.setAccessible(field);
String name = field.getName();
String id = treeConfig.getIdKey();
if (Objects.equals(name, id)) {
return ReflectUtil.getFieldValue(t, field);
}
}
throw new RuntimeException("[TreeUtils.getFields]TreeConfig Error");
}
private static <T> void setChildren(T t, TreeConfig treeConfig, List<T> childrens) {
Field[] fields = ReflectUtil.getFields(t.getClass());
for (Field field : fields) {
ReflectUtil.setAccessible(field);
String name = field.getName();
String children = treeConfig.getChildren();
if (Objects.equals(name, children)) {
ReflectUtil.setFieldValue(t, field, childrens);
}
}
}
}
二丶解析代码
- TreeConfig作用:TreeUtils所需配置
- idKey:当前层级的主键对应的字段名
- parentIdKey:当前层级上一层主键对应的字段名
- children:当前层级装载子节点的容器字段名
- getParentId(),getId():利用反射获取父级id和当前层级id的值
- setChildren:利用反射装载子节点的值
- buildTree():将传进来的集合通过父级id分组,然后获取到顶层的数据集合,开始调用handleTree()方法进行递归封装。
- handleTree():遍历传进来的数据集合,获取每个数据的当前层级id,获取下层节点数据,设置进子节点中,递归调用此方法。
三丶测试代码
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dept {
private Long id;
private Long parentId;
private String name;
public Dept(Long id, Long parentId, String name) {
this.id = id;
this.parentId = parentId;
this.name = name;
}
private List<Dept> children;
}
public class TreeTest {
public static final List<Dept> deptList;
static {
Dept dept1 = new Dept(1l, 0l, "宣传部");
Dept dept2 = new Dept(2l, 0l, "财务部");
Dept dept3 = new Dept(3l, 0l, "采购部");
Dept dept4 = new Dept(null,1l, "宣传部子部门一");
Dept dept5 = new Dept(5l, 1l, "宣传部子部门二");
Dept dept6 = new Dept(6l, 1l, "宣传部子部门三");
Dept dept7 = new Dept(7l, 2l, "财务部子部门一");
Dept dept8 = new Dept(8l, 2l, "财务部子部门二");
Dept dept9 = new Dept(9l, 3l, "采购部子部门一");
Dept dept10 = new Dept(10l, 3l, "采购部子部门二");
deptList = Arrays.asList(dept1, dept2, dept3, dept4, dept5, dept6, dept7, dept8, dept9, dept10);
}
@Test
public void testTreeMe() {
TreeConfig treeConfig = new TreeConfig();
treeConfig.setParentIdKey("parentId");
treeConfig.setIdKey("id");
List<Dept> depts = TreeUtils.buildTree(deptList, 0l, treeConfig);
System.out.println(JSON.toJSONString(depts, true));
}
总结
写这个Utils的初衷也是看到Hutool里面有个TreeUtil的工具类可以实现同样的需求,但是他返回的是一个List<Tree>的结构,我们在代码中还需要去转一下转成我们输出的DTO,所以就想着利用自己知道的知识看能不能写一个同样的工具类。写完之后我也看了一下Hutool的源码,Hutool是利用循坏实现的封装,里面的思想也比我这个工具类妙一些,我这个就是单纯的jdk8和泛型和反射的应用,有兴趣的可以去了解一下Hutool的源码,如果对我这个工具类有什么建议也希望大家在评论区告诉我
*注:工具类里面用到了Htuool的一些反射工具类,我们运行代码是需要导入Hutool的jar包,我们也可以用原生的方法替代里面用到的工具类方法,例如 ReflectUtil.getFields == class.getDeclaredFields()、ReflectUtil.setAccessible(field) == field.setAccessible(true)、ReflectUtil.getFieldValue(t, field) == field.get(t) 、ReflectUtil.setFieldValue(t, field, childrens == field.set(t, childrens)