在日常开发中,我们往往会使用到树形结构数据,以满足我们的需求。常常以
parentId
来进行关联、储存,但是在我们取到这些数据后,又要经过一定的处理才能形成树结构,往往使用children
来保存子集数据。我们不可能每次需要树形结构就重新写一遍处理方法,其实这些处理方法有共同性,都是循环然后递归取值、赋值,我们将这些相同的逻辑进行抽离,然后在往往需要处理的地方添加回调函数,这样既能完美通用的构建树形结构数据,又能够单独处理。
1. 代码
下面是树构建工具,为了方便使用,我们将方法写成了非静态方法。
package com.yunku.common.utils;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 树工具
*
* @author Jalon
* @since 2023/11/2 14:24
**/
@Data
public class TreeUtils<T> {
/**
* ID字段名
*/
private String idField;
/**
* 父级ID字段名
*/
private String parentIdField;
/**
* 子集字段名
*/
private String childField;
/**
* 数据列表
*/
private List<T> objs;
public TreeUtils() {
}
public TreeUtils(String idField, String parentIdField, String childField) {
this.idField = idField;
this.parentIdField = parentIdField;
this.childField = childField;
}
/**
* 过滤掉没有子集的数据
*/
public List<T> filter() {
return this.objs.stream().filter(t -> {
Object fieldValue = getField(t, this.childField);
List<Object> objects = this.objToList(fieldValue);
return Objects.nonNull(objects) && !objects.isEmpty();
}).collect(Collectors.toList());
}
/**
* 构建树形结构
*
* @param objs 数据列表
*/
public TreeUtils<T> buildTree(List<T> objs) {
buildTree(objs, null, null);
return this;
}
/**
* 构建树形结构
*
* @param objs 数据列表
*/
public TreeUtils<T> buildTree(List<T> objs, Function<MapperParam, T> mapper) {
buildTree(objs, mapper, null);
return this;
}
/**
* 构建树形结构
*
* @param objs 数据列表
*/
public void buildTreeLeaf(List<T> objs, Function<T, T> leafMapper) {
buildTree(objs, null, leafMapper);
}
/**
* 构建树形结构
*
* @param objs 数据列表
*/
public void buildTree(List<T> objs, Function<MapperParam, T> mapper, Function<T, T> leafMapper) {
try {
for (T obj : objs) {
toBuild(objs, obj, mapper, leafMapper);
}
this.objs = objs;
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
/**
* 递归赋值
*/
public void toBuild(List<T> objs, T obj, Function<MapperParam, T> mapper, Function<T, T> leafMapper) throws NoSuchFieldException, IllegalAccessException {
List<T> childrenList = Lists.newArrayList();
for (T t : objs) {
Object value = getField(t, this.parentIdField);
Object value2 = getField(obj, this.idField);
if (Objects.nonNull(value) && value.equals(value2)) {
childrenList.add(t);
}
}
if (!childrenList.isEmpty()) {
setField(obj, this.childField, childrenList);
for (T children : childrenList) {
if (Objects.nonNull(mapper)) {
toBuild(objs, mapper.apply(new MapperParam(children, obj)), mapper, leafMapper);
} else {
toBuild(objs, children, mapper, leafMapper);
}
}
} else {
if (Objects.nonNull(leafMapper)) {
leafMapper.apply(obj);
}
}
}
/**
* 获取属性值
*
* @param obj 类
* @return 值
*/
private Object getField(T obj, String fieldName) {
try {
Class<?> clazz = obj.getClass();
List<Field> fieldList = getFieldByCurrentAndSuper(clazz);
if (fieldList.size() > 0) {
for (Field field : fieldList) {
if (field.getName().equals(fieldName)) {
// 设置可访问私有属性
field.setAccessible(true);
return field.get(obj);
}
}
}
} catch (Exception ignored) {
}
return null;
}
/**
* 赋值属性
*/
private static <T, S> void setField(T obj, String fieldName, S value) throws IllegalAccessException {
try {
Class<?> clazz = obj.getClass();
List<Field> fieldList = getFieldByCurrentAndSuper(clazz);
if (fieldList.size() > 0) {
for (Field field : fieldList) {
if (field.getName().equals(fieldName)) {
// 设置可访问私有属性
field.setAccessible(true);
field.set(obj, value);
}
}
}
} catch (Exception ignored) {
}
}
/**
* description: 从当前以及父类中获取全部字段
*
* @param clazz 属性所在类
* @return java.lang.reflect.Field
*/
public static List<Field> getFieldByCurrentAndSuper(Class<?> clazz) {
List<Field> fields = new ArrayList<>();
getFieldByCurrentAndSuper(clazz, fields);
return fields;
}
/**
* description: 从当前以及父类中获取全部字段
*
* @param clazz 属性所在类
* @return java.lang.reflect.Field
*/
private static List<Field> getFieldByCurrentAndSuper(Class<?> clazz, List<Field> fields) {
Field[] declaredFields = clazz.getDeclaredFields();
fields.addAll(Arrays.asList(declaredFields));
if (!clazz.equals(Object.class)) {
return getFieldByCurrentAndSuper(clazz.getSuperclass(), fields);
}
return fields;
}
/**
* 对象转List
*/
public List<Object> objToList(Object obj) {
if (obj instanceof ArrayList<?>) {
return new ArrayList<Object>((List<?>) obj);
}
return null;
}
@Data
@AllArgsConstructor
public class MapperParam {
/**
* 当前数据
*/
private T current;
/**
* 父级数据
*/
private T parent;
}
}
2. 使用
使用方式也很简单。
设我们有以下数据
List<Book> books = bookMapper.list();
2.1 常规使用
TreeUtils<HcBayonetVo> treeUtils = new TreeUtils<>("bookId", "parentId", "children");
treeUtils.buildTree(books);
// 直接返回,包含所有数据,且包含子集数据
return books;
// 如果想只返回有子集的数据
return books.filter();
2.2 数据项处理
注意:回调不会处理根节点数据项,需要单独循环处理
对于子集,我们直接添加回调即可
treeUtils.buildTree(list, mapperParam -> {
// mapperParam 拥有两个参数,current 为当前数据项,parent 为当前数据项的父级数据项
Book parent = mapperParam.getParent();
Book current = mapperParam.getCurrent();
current.setLevel(parent.getLevel() + 1);
return current;
});
叶子节点我们也可以单独处理
treeUtils.buildTreeLeaf(list, book -> {
book.setLeaf(true);
return book;
});
下面是完整的处理(给每一项添加级别 level, 叶子节点添加叶子标识 leaf)
for (Book book : list) {
book.setLevel(1);
}
TreeUtils<Book> treeUtils = new TreeUtils<>("bayonetId", "parentId", "children");
treeUtils.buildTree(books, mapperParam -> {
Book parent = mapperParam.getParent();
Book current = mapperParam.getCurrent();
current.setLevel(parent.getLevel() + 1);
return current;
}, book -> {
book.setLeaf(true);
return book;
});
return treeUtils.filter();