概要:
以前我们提供了针对企业微信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}]