List、Tree互转工具类

概要:
以前我们提供了针对企业微信API的部门列表进行转换的工具类,这里提供一个更加通用的,不依赖于具体Java类的工具类。

代码:
Util类:

package com.example.study.util;

import org.springframework.util.Assert;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * List Tree互转通过方法
 * 1.树不能有环,否则会陷入死循环
 * 2.节点列表不能有null的节点,否则会抛出NPE
 */
public class ListTreeConvertUtil {
    /**
     * 获取最上层节点
     *
     * @param list        节点列表
     * @param parentField 父节点id字段名称
     * @param idField     当前节点id字段名称
     * @param <T>         节点类型
     * @return 最上层节点
     */
    public static <T> T getTopElement(List<T> list, String parentField, String idField) {
        Assert.notEmpty(list, "list is required!");
        Assert.hasText(parentField, "parentField is required!");
        Assert.hasText(idField, "idField is required!");
        list = list.stream().filter(o -> o != null).collect(Collectors.toList());
        Set<Object> ids = list.stream()
                .map(o -> getFieldValue(o, idField))
                .filter(o -> o != null)
                .collect(Collectors.toSet());
        for (T element : list) {
            if (!ids.contains(getFieldValue(element, parentField))) {
                return element;
            }
        }
        throw new RuntimeException("can not get top element!");
    }

    /**
     * 获取所有上级节点
     *
     * @param list           list
     * @param currentElement 当前节点
     * @param parentField    父节点id字段名称
     * @param idField        当前节点id字段名称
     * @param <T>            节点类型
     * @return 所有上级节点
     */
    public static <T> List<T> getParentElements(List<T> list, T currentElement, String parentField, String idField) {
        Assert.notNull(list, "list is required!");
        Assert.notNull(currentElement, "currentElement is required!");
        Assert.hasText(parentField, "parentField is required!");
        Assert.hasText(idField, "idField is required!");
        List<T> parentElements = new ArrayList<>();
        list = list.stream().filter(o -> o != null).collect(Collectors.toList());
        if (list.isEmpty()) {
            return parentElements;
        }

        Map<Object, T> idElementMap = list.stream()
                .collect(Collectors.toMap(o -> getFieldValue(o, idField), Function.identity()));
        T topElement = getTopElement(list, parentField, idField);
        Object topElementId = getFieldValue(topElement, idField);
        T parentElement = idElementMap.get(getFieldValue(currentElement, parentField));
        while (parentElement != null) {
            parentElements.add(parentElement);
            if (topElementId.equals(getFieldValue(parentElement, idField))) {
                break;
            }
            parentElement = idElementMap.get(getFieldValue(parentElement, parentField));
        }
        return parentElements;
    }

    /**
     * 获取所有下级节点
     *
     * @param list           list
     * @param currentElement 当前节点
     * @param parentField    父节点id字段名称
     * @param idField        当前节点id字段名称
     * @param <T>            节点类型
     * @return 所有下级节点
     */
    public static <T> List<T> getChildElements(List<T> list, T currentElement, String parentField, String idField) {
        Assert.notNull(list, "list is required!");
        Assert.notNull(currentElement, "currentElement is required!");
        Assert.hasText(parentField, "parentField is required!");
        Assert.hasText(idField, "idField is required!");
        List<T> childElements = new ArrayList<>();
        list = list.stream().filter(o -> o != null).collect(Collectors.toList());
        if (list.isEmpty()) {
            return childElements;
        }
        Map<Object, List<T>> parentChildMap = getParentChildMap(list, parentField);
        doGetChildElements(currentElement, idField, parentChildMap, childElements);
        return childElements;
    }

    /**
     * 递归获取当前节点的所有子节点
     *
     * @param currentElement 当前节点
     * @param idField        当前节点id字段名称
     * @param parentChildMap 父节点id -> 子节点 Map
     * @param childElements  子节点列表
     * @param <T>            节点类型
     */
    private static <T> void doGetChildElements(T currentElement, String idField, Map<Object, List<T>> parentChildMap, List<T> childElements) {
        List<T> childs = parentChildMap.get(getFieldValue(currentElement, idField));
        if (childs == null) {
            return;
        }
        childElements.addAll(childs);
        for (T child : childs) {
            doGetChildElements(child, idField, parentChildMap, childElements);
        }
    }

    /**
     * tree转list
     *
     * @param root       tree的根节点
     * @param childField 子节点列表字段名称
     * @param <T>        节点类型
     * @return list
     */
    public static <T> List<T> treeToList(T root, String childField) {
        Assert.notNull(root, "root is required!");
        Assert.hasText(childField, "childField is required!");
        List<T> list = new ArrayList<>();
        doGetList(root, childField, list);
        // 将子节点列表的字段值置空
        List<Object> emptyList = new ArrayList<>();
        for (T element : list) {
            setFieldValue(element, childField, emptyList);
        }
        return list;
    }

    /**
     * 递归获取子节点
     *
     * @param root       根节点
     * @param childField 子节点列表字段名称
     * @param list       list
     * @param <T>        节点类型
     */
    private static <T> void doGetList(T root, String childField, List<T> list) {
        if (root == null) {
            return;
        }
        list.add(root);
        List<T> childs = (List<T>) getFieldValue(root, childField);
        if (childs == null) {
            return;
        }
        for (T child : childs) {
            if (child == null) {
                continue;
            }
            doGetList(child, childField, list);
        }
    }

    /**
     * list转tree
     *
     * @param list        list
     * @param root        tree的根节点
     * @param parentField 父节点id字段名称
     * @param idField     当前节点id字段名称
     * @param childField  子节点列表字段名称
     * @param <T>         节点类型
     */
    public static <T> void listToTree(List<T> list, T root, String parentField, String idField, String childField) {
        Assert.notEmpty(list, "list is required!");
        Assert.notNull(root, "root is required!");
        Assert.hasText(parentField, "parentField is required!");
        Assert.hasText(idField, "idField is required!");
        Assert.hasText(childField, "childField is required!");
        list = list.stream().filter(o -> o != null).collect(Collectors.toList());
        Map<Object, List<T>> parentChildMap = getParentChildMap(list, parentField);
        if (parentChildMap.isEmpty()) {
            return;
        }
        doGetTree(root, idField, childField, parentChildMap);
    }

    /**
     * 递归设置子节点属性
     *
     * @param root           根节点
     * @param idField        当前节点id字段名称
     * @param childField     子节点列表字段名称
     * @param parentChildMap 父节点id -> 子节点 Map
     * @param <T>            节点类型
     */
    private static <T> void doGetTree(T root, String idField, String childField, Map<Object, List<T>> parentChildMap) {
        Object rootId = getFieldValue(root, idField);
        List<T> childs = parentChildMap.get(rootId);
        if (childs == null) {
            childs = new ArrayList<>();
        }
        setFieldValue(root, childField, childs);
        for (T child : childs) {
            doGetTree(child, idField, childField, parentChildMap);
        }
    }

    /**
     * 获取父节点id -> 子节点 Map
     *
     * @param elements    节点列表
     * @param parentField 父节点id字段名称
     * @param <T>         节点类型
     * @return 父节点id -> 子节点 Map
     */
    private static <T> Map<Object, List<T>> getParentChildMap(List<T> elements, String parentField) {
        Map<Object, List<T>> parentChildMap = new HashMap<>();
        for (T element : elements) {
            Object parentId = getFieldValue(element, parentField);
            if (parentId == null) {
                continue;
            }
            List<T> childs = parentChildMap.get(parentId);
            if (childs == null) {
                childs = new ArrayList<>();
                parentChildMap.put(parentId, childs);
            }
            childs.add(element);
        }
        return parentChildMap;
    }

    /**
     * 获取字段值
     *
     * @param element   待获取值的对象
     * @param fieldName 字段名称
     * @param <T>       对象类型
     * @return 字段值
     * @throws NoSuchFieldException   字段不存在
     * @throws IllegalAccessException 没有字段访问权限
     */
    private static <T> Object getFieldValue(T element, String fieldName) {
        try {
            Field field = element.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            return field.get(element);
        } catch (NoSuchFieldException | IllegalAccessException exception) {
            throw new RuntimeException(exception.getMessage());
        }
    }

    /**
     * 设置字段值
     *
     * @param element   待设置值的对象
     * @param fieldName 字段名称
     * @param value     字段值
     * @param <T>       对象类型
     * @throws NoSuchFieldException   字段不存在
     * @throws IllegalAccessException 没有字段访问权限
     */
    private static <T> void setFieldValue(T element, String fieldName, Object value) {
        try {
            Field field = element.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(element, value);
        } catch (NoSuchFieldException | IllegalAccessException exception) {
            throw new RuntimeException(exception.getMessage());
        }
    }
}

测试;
部门信息类:

package com.example.study.entity;

import lombok.Data;

import java.util.List;

/**
 * 部门实体类
 */
@Data
public class DepartmentEntity {
    /**
     * 部门id
     */
    private Integer id;
    /**
     * 部门名称
     */
    private String name;
    /**
     * 上级部门id
     */
    private Integer parentId;

    /**
     * 子部门列表
     */
    private List<DepartmentEntity> childs;
}

测试类:

package com.example.study.test;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.example.study.entity.DepartmentEntity;
import com.example.study.util.ListTreeConvertUtil;

import java.util.List;

public class DepartmentTest {
    public static void main(String[] args) {
        String departmentsStr = "[{\"id\":1,\"name\":\"one\",\"parentId\":null},{\"id\":2,\"name\":\"two\",\"parentId\":1},{\"id\":3,\"name\":\"three\",\"parentId\":1},{\"id\":4,\"name\":\"four\",\"parentId\":1},{\"id\":5,\"name\":\"five\",\"parentId\":2},{\"id\":6,\"name\":\"six\",\"parentId\":2},{\"id\":7,\"name\":\"seven\",\"parentId\":3},{\"id\":8,\"name\":\"eight\",\"parentId\":4}]";
        List<DepartmentEntity> departments = JSONArray.parseArray(departmentsStr, DepartmentEntity.class);
        departments.add(null);
        DepartmentEntity topElement = ListTreeConvertUtil.getTopElement(departments, "parentId", "id");
        System.out.println("topElement:" + JSON.toJSONString(topElement));
        DepartmentEntity currentDepartment = departments.get(1);
        System.out.println("currentDepartment:" + JSON.toJSONString(currentDepartment));
        ListTreeConvertUtil.listToTree(departments, topElement, "parentId", "id", "childs");
        System.out.println("tree:" + JSON.toJSONString(topElement));
        DepartmentEntity tmp = new DepartmentEntity();
        topElement.getChilds().add(1, null);
        topElement.getChilds().get(0).getChilds().get(0).setChilds(null);
        topElement.getChilds().get(0).getChilds().get(1).getChilds().add(null);
        topElement.getChilds().get(0).getChilds().get(1).getChilds().add(tmp);
        List<DepartmentEntity> parentElements = ListTreeConvertUtil.getParentElements(departments, currentDepartment, "parentId", "id");
        System.out.println("parentElements:" + JSON.toJSONString(parentElements));
        List<DepartmentEntity> childElements = ListTreeConvertUtil.getChildElements(departments, currentDepartment, "parentId", "id");
        System.out.println("childElements:" + JSON.toJSONString(childElements));
        List<DepartmentEntity> childs = ListTreeConvertUtil.treeToList(topElement, "childs");
        System.out.println("childs:" + JSON.toJSONString(childs, SerializerFeature.DisableCircularReferenceDetect));
    }
}

测试结果:

topElement:{"id":1,"name":"one"}
currentDepartment:{"id":2,"name":"two","parentId":1}
tree:{"childs":[{"childs":[{"childs":[],"id":5,"name":"five","parentId":2},{"childs":[],"id":6,"name":"six","parentId":2}],"id":2,"name":"two","parentId":1},{"childs":[{"childs":[],"id":7,"name":"seven","parentId":3}],"id":3,"name":"three","parentId":1},{"childs":[{"childs":[],"id":8,"name":"eight","parentId":4}],"id":4,"name":"four","parentId":1}],"id":1,"name":"one"}
parentElements:[{"childs":[{"childs":[{"id":5,"name":"five","parentId":2},{"childs":[null,{}],"id":6,"name":"six","parentId":2}],"id":2,"name":"two","parentId":1},null,{"childs":[{"childs":[],"id":7,"name":"seven","parentId":3}],"id":3,"name":"three","parentId":1},{"childs":[{"childs":[],"id":8,"name":"eight","parentId":4}],"id":4,"name":"four","parentId":1}],"id":1,"name":"one"}]
childElements:[{"id":5,"name":"five","parentId":2},{"childs":[null,{}],"id":6,"name":"six","parentId":2}]
childs:[{"childs":[],"id":1,"name":"one"},{"childs":[],"id":2,"name":"two","parentId":1},{"childs":[],"id":5,"name":"five","parentId":2},{"childs":[],"id":6,"name":"six","parentId":2},{"childs":[]},{"childs":[],"id":3,"name":"three","parentId":1},{"childs":[],"id":7,"name":"seven","parentId":3},{"childs":[],"id":4,"name":"four","parentId":1},{"childs":[],"id":8,"name":"eight","parentId":4}]

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值