利用泛型和反射实现TreeUtils

344 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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);
            }
        }
    }


}

二丶解析代码

  1. TreeConfig作用:TreeUtils所需配置
  • idKey:当前层级的主键对应的字段名
  • parentIdKey:当前层级上一层主键对应的字段名
  • children:当前层级装载子节点的容器字段名
  1. getParentId(),getId():利用反射获取父级id和当前层级id的值
  2. setChildren:利用反射装载子节点的值
  3. buildTree():将传进来的集合通过父级id分组,然后获取到顶层的数据集合,开始调用handleTree()方法进行递归封装。
  4. 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)