版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhangpan_soft/article/details/82887827
- 因为项目需求,修改将如下数据格式的数据转为树结构数据:
id | parent |
---|---|
1 | null |
2 | null |
3 | null |
4 | 1 |
5 | 1 |
6 | 1 |
7 | 2 |
8 | 2 |
9 | 3 |
10 | 4 |
11 | 7 |
12 | 5 |
13 | 10 |
14 | 8 |
15 | 11 |
16 | 12 |
17 | 13 |
- 将这样拥有父子依赖关系的数据转化为多叉树,本来想在网上找个现成代码的,百度半天,发现,说的云的雾的,思路感觉都很混乱,因此决定自己动手造轮子,因为我是管理整个项目架构的,所以这时候如果这个地方用,写一个,呢个地方用再写一个,明显不太科学,因此,我做了一个工具类,此工具类目前只提供2种功能,其一就是将目标集合转成多叉树集合,其二就是给目标节点添加孩子,至于其他的功能,因为项目中用不到,没有进行封装,后续如果有小伙伴需要用到多叉树的遍历,查找,排序,插入,删除功能的话再进行封装,大家需要用到这些功能可以给我留言,我在闲暇时间可能给你们封装一套
- 好了废话少说,先上代码,代码中注释非常清晰,几乎逐行注释,所以在这里不多做代码的解释
package com.rjhcsoft.credit.utils;
import com.alibaba.fastjson.JSON;
import javax.validation.constraints.NotNull;
import java.lang.reflect.Field;
import java.util.*;
public class TreeUtils {
/**
* 集合转树结构
* @param collection 目标集合
* @param clazz 集合元素类型
* @param <T>
* @return
*/
public static <T> Collection<T> toTree(@NotNull Collection<T> collection,@NotNull Class<T> clazz){
return toTree(collection,null,null,null,clazz);
}
/**
* 集合转树结构
* @param collection 目标集合
* @param id 被依赖字段名称
* @param parent 依赖字段名称
* @param children 子节点集合属性名称
* @param clazz 集合元素类型
* @param <T>
* @return
*/
public static <T> Collection<T> toTree(@NotNull Collection<T> collection,String id,String parent,String children,@NotNull Class<T> clazz){
try {
if (collection==null || collection.isEmpty()) return null;// 如果目标集合为空,直接返回一个空树
if (StringUtil.isEmpty(id)) id = "id";// 如果被依赖字段名称为空则默认为id
if (StringUtil.isEmpty(parent)) parent = "parent";// 如果依赖字段为空则默认为parent
if (StringUtil.isEmpty(children)) children = "children";// 如果子节点集合属性名称为空则默认为children
Collection<T> roots = null;// 初始化根节点集合
if (collection.getClass().isAssignableFrom(Set.class)) roots = new HashSet<>();// 如果目标节点是一个set集合,则初始化根节点集合为hashset
else roots = new ArrayList<>();// 否则初始化为Arraylist,
// 这里集合初始化只分2中,要么是hashset,要么ArrayList,因为这两种最常用,其他不常用的摒弃
Field idField = clazz.getDeclaredField(id);// 获取依赖字段
Field parentField = clazz.getDeclaredField(parent);// 获取被依赖字段
Field childrenField = clazz.getDeclaredField(children);// 获取孩子字段
// 设置为可访问
idField.setAccessible(true);
parentField.setAccessible(true);
childrenField.setAccessible(true);
// 找出所有的根节点
for (T c:collection){
Object o = parentField.get(c);
if (o instanceof String){
if (StringUtil.isEmpty((String) o)) {// 如果父节点为空则说明是根节点,添加到根节点集合
roots.add(c);
}
}else {
if (o==null){
roots.add(c);
}
}
}
// 从目标集合移除所有根节点
collection.removeAll(roots);
for (T c:roots){// 遍历根节点,依次添加子节点
addChild(c,collection,idField,parentField,childrenField);
}
// 关闭可访问
idField.setAccessible(false);
parentField.setAccessible(false);
childrenField.setAccessible(false);
return roots;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 为目标节点添加孩子节点,此方法为私有,不能为公开,否则类修改信息无法恢复,后面有公开方法,其专门为目标节点添加子节点
* @param c 目标节点
* @param collection 目标集合
* @param idField
* @param parentField
* @param childrenField
* @param <T>
* @throws IllegalAccessException
*/
private static <T> void addChild(@NotNull T c,@NotNull Collection<T> collection,@NotNull Field idField,@NotNull Field parentField,@NotNull Field childrenField) throws IllegalAccessException {
Object id = idField.get(c);// 获取目标节点的被依赖值
Collection<T> children = (Collection<T>) childrenField.get(c);// 获取目标节点的孩子列表
for (T cc:collection){// 遍历目标集合
Object o = parentField.get(cc);// 获取当前节点的依赖值
if (id.equals(o)){// 如果当前节点的被依赖值和目标节点的被依赖值相等,则说明,当前节点是目标节点的子节点
if (children==null) {// 如果目标节点的孩子集合为null,初始化目标节点的孩子集合
if (collection.getClass().isAssignableFrom(Set.class)){// 如果目标集合是一个set集合,则初始化目标节点的孩子节点集合为set
children = new HashSet<>();
}else children = new ArrayList<>();// 否则初始化为list
}
// 将当前节点添加到目标节点的孩子节点
children.add(cc);
// 重设目标节点的孩子节点集合,这里必须重设,因为如果目标节点的孩子节点是null的话,这样是没有地址的,就会造成数据丢失,所以必须重设,如果目标节点所在类的孩子节点初始化为一个空集合,而不是null,则可以不需要这一步,因为java一切皆指针
childrenField.set(c,children);
// 递归添加孩子节点
addChild(cc,collection,idField,parentField,childrenField);
}
}
// 特别说明:大家可以看到此递归没有明显出口,其出口就是是否当前节点的依赖值和目标节点的被依赖值一样,一样就递归,不一样进不了if,自然出递归
// 此工具类自我感觉是最简单的,最实用的工具类,我看网上许多人写的,都是云的雾的,本来也想借鉴,但是实在没一个能看的感觉思路清晰,没办法,自己动手造轮子
}
/**
* 为目标节点添加孩子
* @param c 目标节点
* @param collection 目标集合
* @param id 被依赖字段名
* @param parent 依赖字段名
* @param children 孩子节点字段名
* @param clazz 集合元素所在类别
* @param <T>
*/
public static <T> void addChild(@NotNull T c,@NotNull Collection<T> collection,String id,String parent,String children,@NotNull Class<T> clazz){
try {
if (collection==null || collection.isEmpty()) return ;// 如果目标集合为空,直接返回一个空树
if (StringUtil.isEmpty(id)) id = "id";// 如果被依赖字段名称为空则默认为id
if (StringUtil.isEmpty(parent)) parent = "parent";// 如果依赖字段为空则默认为parent
if (StringUtil.isEmpty(children)) children = "children";// 如果子节点集合属性名称为空则默认为children
Field idField = clazz.getDeclaredField(id);// 获取依赖字段
Field parentField = clazz.getDeclaredField(parent);// 获取被依赖字段
Field childrenField = clazz.getDeclaredField(children);// 获取孩子字段
// 设置为可访问
idField.setAccessible(true);
parentField.setAccessible(true);
childrenField.setAccessible(true);
addChild(c,collection,idField,parentField,childrenField);
// 关闭可访问
idField.setAccessible(false);
parentField.setAccessible(false);
childrenField.setAccessible(false);
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 为目标节点添加孩子
* @param c 目标节点
* @param collection 目标集合
* @param clazz 集合元素所在类型
* @param <T>
*/
public static <T> void addChild(@NotNull T c,@NotNull Collection<T> collection,@NotNull Class<T> clazz){
addChild(c,collection,null,null,null,clazz);
}
public static void main(String[] args){
List<Menu> list = new ArrayList<>();
list.add(new Menu(1,null));
list.add(new Menu(2,null));
list.add(new Menu(3,null));
list.add(new Menu(4,1));
list.add(new Menu(5,1));
list.add(new Menu(6,1));
list.add(new Menu(7,2));
list.add(new Menu(8,2));
list.add(new Menu(9,3));
list.add(new Menu(10,4));
list.add(new Menu(11,7));
list.add(new Menu(12,5));
list.add(new Menu(13,10));
list.add(new Menu(14,8));
list.add(new Menu(15,11));
list.add(new Menu(16,12));
list.add(new Menu(17,13));
Collection<Menu> menus = TreeUtils.toTree(list, null, null, null, Menu.class);
System.out.println(JSON.toJSONString(menus));
}
}
class Menu{
private Integer id;
private Integer parent;
private List<Menu> children;
public Menu() {
}
public Menu(Integer id, Integer parent) {
this.id = id;
this.parent = parent;
}
public List<Menu> getChildren() {
return children;
}
public void setChildren(List<Menu> children) {
this.children = children;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getParent() {
return parent;
}
public void setParent(Integer parent) {
this.parent = parent;
}
}
- 最终打印的json数据如下
格式化之前
[{"children":[{"children":[{"children":[{"children":[{"id":"17","parent":"13"}],"id":"13","parent":"10"}],"id":"10","parent":"4"}],"id":"4","parent":"1"},{"children":[{"children":[{"id":"16","parent":"12"}],"id":"12","parent":"5"}],"id":"5","parent":"1"},{"id":"6","parent":"1"}],"id":"1"},{"children":[{"children":[{"children":[{"id":"15","parent":"11"}],"id":"11","parent":"7"}],"id":"7","parent":"2"},{"children":[{"id":"14","parent":"8"}],"id":"8","parent":"2"}],"id":"2"},{"children":[{"id":"9","parent":"3"}],"id":"3"}]
格式化之后
[
{
"children": [
{
"children": [
{
"children": [
{
"children": [
{
"children": [],
"id": "17",
"parent": "13"
}
],
"id": "13",
"parent": "10"
}
],
"id": "10",
"parent": "4"
}
],
"id": "4",
"parent": "1"
},
{
"children": [
{
"children": [
{
"children": [],
"id": "16",
"parent": "12"
}
],
"id": "12",
"parent": "5"
}
],
"id": "5",
"parent": "1"
},
{
"children": [],
"id": "6",
"parent": "1"
}
],
"id": "1"
},
{
"children": [
{
"children": [
{
"children": [
{
"children": [],
"id": "15",
"parent": "11"
}
],
"id": "11",
"parent": "7"
}
],
"id": "7",
"parent": "2"
},
{
"children": [
{
"children": [],
"id": "14",
"parent": "8"
}
],
"id": "8",
"parent": "2"
}
],
"id": "2"
},
{
"children": [
{
"children": [],
"id": "9",
"parent": "3"
}
],
"id": "3"
}
]