一、前言
在Java中,如果你有一个表示父子关系的列表,并且想要把这个列表转成一个子父集list列表树目录,一般来说想要把list列表转成一个子父集列表,这个对象需要在属性中必须要有几个字段,id(节点id)、parentId(指向父节点id)、children(子节点),通过三个字段可以组装成一个子父集列表目录。
二、代码示例
- 创建Node节点对象类
package com.demo.terrutils;
import lombok.Data;
import java.util.List;
/**
* 文件名:Node
* 创建者:
* 创建时间:
* 描述:
*/
@Data
public class Node {
/**
* id
*/
private String id;
/**
* 名称
*/
private String name;
/**
* 父id
*/
private String parentId;
/**
* 排序字段
*/
private Integer sortNumber;
/**
* 子节点
*/
private List<Node> children;
}
- 创建TreeUtil工具类
package com.demo.terrutils;
import io.micrometer.common.util.StringUtils;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 文件名:TreeUtil
* 创建者:
* 创建时间:
* 描述:list列表转子父节点列表工具类
*/
public final class TreeUtil {
//私有化构造,防止客户端通过 new 创建工具对象
private TreeUtil(){
}
/**
* 获取当前节点列表及子节点
* @param list 集合列表
* @param id 节点id值
* @return
* @param <T>
*/
public static <T> List<T> buildTreeByMap(List<T> list,String id) {
//要求这个 <T> 泛型对象中的属性必须要和 id、parentId、children、sortNumber 保持名称一样
return buildTreeByMap(list, "id", "parentId", "children","sortNumber", id);
}
/**
* 通过 stream map 分组方式构建树
* @param list 列表
* @param idName id名称
* @param parentIdName 父id名称
* @param childrenName 子节点列表名称
* @param sortNumber 排序字段
* @param root 顶层节点父id的值
* @return
* @param <T>
*/
public static <T> List<T> buildTreeByMap(List<T> list, String idName, String parentIdName, String childrenName,String sortNumber ,String root) {
if (StringUtils.isBlank(idName) || StringUtils.isBlank(parentIdName) || StringUtils.isBlank(childrenName) || StringUtils.isBlank(sortNumber)) {
return Collections.emptyList();
}
//1.排序分组
Map<String, List<T>> mapList = list.stream()
//根据sortNumber字段排序
.sorted(Comparator.comparing(o-> (Integer)getFieldValue(o, sortNumber),
//如果 sortNumber 字段为 null 则放在最后,不加这个会直接抛出异常
Comparator.nullsLast(Integer::compareTo)))
//根据parentId进行分组
.collect(Collectors.groupingBy(o -> getFieldValue(o, parentIdName).toString()));
//2.给每个节点设置子节点列表
list.forEach(node -> setFieldValue(node, mapList.get(getFieldValue(node, idName).toString()), childrenName));
//3.这个排序是因为返回的是多个根节点,需要单独在重新排序下
List<T> treeList = list.stream().filter(o -> root.equals(getFieldValue(o, parentIdName)))
.sorted(Comparator.comparing(o-> (Integer)getFieldValue(o, sortNumber),
Comparator.nullsLast(Integer::compareTo)))
.collect(Collectors.toList());
return treeList;
}
/**
* 获取属性值
* @param o 对象
* @param fieldName 属性名
* @return {@link String}
*/
private static Object getFieldValue(Object o, String fieldName) {
try {
Class<?> oClass = o.getClass();
Field field = oClass.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(o);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
/**
* 设置字段值
* @param o 对象
* @param val 值
* @param fieldName 属性名
*/
private static void setFieldValue(Object o, Object val, String fieldName) {
try {
Class<?> oClass = o.getClass();
Field field = oClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(o, val);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
- 创建Main测试类
package com.demo.terrutils;
import java.util.ArrayList;
import java.util.List;
/**
* 文件名:Main
* 创建者:
* 创建时间:
* 描述:
*/
public class Main {
public static void main(String[] args) {
//1.初始化列表
List<Node> listNode = intListTest();
//2.列表转为子父列表树结构
List<Node> treeList = TreeUtil.buildTreeByMap(listNode,"0");
//3.遍历树list
treeList.stream().forEach(node->{
traverseNode(node);
});
}
public static void traverseNode(Node node) {
if (node == null) {
return;
}
// 处理当前节点,例如打印节点信息
System.out.println(node.getName() + " 排序字段 " +node.getSortNumber());
// 如果存在子节点,递归遍历每个子节点
if (node.getChildren() != null && !node.getChildren().isEmpty()) {
for (Node child : node.getChildren()) {
traverseNode(child);
}
}
}
private static List<Node> intListTest(){
List<Node> nodeTestList = new ArrayList<>();
Node test = new Node();
test.setId("1");
test.setParentId("0");
test.setName("第一层1");
test.setSortNumber(9);
Node test1 = new Node();
test1.setId("2");
test1.setParentId("0");
test1.setName("第一层2");
test1.setSortNumber(7);
Node test2 = new Node();
test2.setId("3");
test2.setParentId("0");
test2.setName("第一层3");
test2.setSortNumber(8);
Node test3 = new Node();
test3.setId("01");
test3.setName(" 第二层1");
test3.setParentId("1");
test3.setSortNumber(7);
Node test4 = new Node();
test4.setId("02");
test4.setName(" 第二层2");
test4.setParentId("1");
test4.setSortNumber(5);
Node test5 = new Node();
test5.setId("03");
test5.setName(" 第二层3");
test5.setParentId("2");
test5.setSortNumber(2);
Node test6 = new Node();
test6.setId("001");
test6.setName(" 第三层1");
test6.setParentId("01");
test6.setSortNumber(12);
Node test7 = new Node();
test7.setId("002");
test7.setName(" 第三层2");
test7.setParentId("01");
test7.setSortNumber(23);
Node test8 = new Node();
test8.setId("003");
test8.setName(" 第三层3");
test8.setParentId("03");
test8.setSortNumber(17);
Node test9 = new Node();
test9.setId("004");
test9.setName(" 第三层4");
test9.setParentId("03");
test9.setSortNumber(2);
nodeTestList.add(test);
nodeTestList.add(test1);
nodeTestList.add(test2);
nodeTestList.add(test3);
nodeTestList.add(test4);
nodeTestList.add(test5);
nodeTestList.add(test6);
nodeTestList.add(test7);
nodeTestList.add(test8);
nodeTestList.add(test9);
return nodeTestList;
}
}
- 测试结果