将List转换为树形结构

在 Java 中,把List 转换为多级的树形结构非常常见。例如菜单数据是在数据库中“平铺”存储的,在做查询时,需要将其转换为树形结构,方便前端进行展示。

本文就以菜单作为案例,读者可以举一反三,实现自己的需求

数据库设计

将其命名为PERMISSIONS

字段名 类型 注释
CODE varchar 编码(主键)
PARENT_CODE varchar 父编码
NAME varchar 名称
TYPE tinyint 类型 0菜单 1按钮 2接口
URL varchar 前端路由或后端接口路由
CREATE_TIME datetime 创建时间
UPDATE_TIME datetime 更新时间

Java 处理

假设我们此时已经查询出一个列表List<Permissions>Permissions类是与数据库对应的实体类,包含了PERMISSIONS表的所有字段,此刻就需要对其进行树形结构处理。

树形结构实体类

首先我们需要一个包含自身的树形结构实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 权限树内容
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@ApiModel(description = "权限树内容")
public class PermissionsTreeRes extends Permissions implements Serializable {
private static final long serialVersionUID = 3107598961006613967L;

/**
* 权限树
*/
@ApiModelProperty("权限树")
private List<PermissionsTreeRes> children;
}

PermissionsTreeRes继承了Permissions,即拥有了数据库表中的所有字段。其自身又包含了类型为List<PermissionsTreeRes>children属性,即子节点。

代码处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public List<PermissionsTreeRes> buildPermissionsTree(List<Permissions> permissionsList) {
return permissionsList.stream().filter(permissions -> ObjectUtils.isEmpty(permissions.getParentCod()))
.map(permissions -> {
PermissionsTreeRes treeRes = new PermissionsTreeRes();
BeanUtils.copyProperties(permissions, treeRes);
treeRes.setChildren(this.getPermissionsChildrenList(permissions.getCode(),permissionsList));
return treeRes;
}).collect(Collectors.toList());
}

private List<PermissionsTreeRes> getPermissionsChildrenList(String code, List<Permissions>permissionsList) {
return permissionsList.stream().filter(permissions -> StringUtils.equals(permissions.getParentCode(),code))
.map(permissions -> {
PermissionsTreeRes treeRes = new PermissionsTreeRes();
BeanUtils.copyProperties(permissions, treeRes);
treeRes.setChildren(this.getPermissionsChildrenList(permissions.getCode(),permissionsList));
return treeRes;
}).collect(Collectors.toList());
}
  • buildPermissionsTree()方法内使用ObjectUtils.isEmpty(permissions.getParentCod())条件筛选出所有父编码为空的数据,即一级菜单数据。遍历中的setChildren()调用了getPermissionsChildrenList()方法,并传入自身的CODE,去寻找自己的子节点。

  • getPermissionsChildrenList()方法内使用递归不断的向下查找,直至filter后再无数据,便停止递归,进入下一次遍历。