业务场景
需要将集合转换为树形结构的集合,常见的有菜单树、部门树
最后实现的效果
List<MenuTreeVo> builder = TreeBuilder.<Menu, MenuTreeVo>build(menus)
.convert(MenuConvert.INSTANCE::convertTree)
.builder();
前置知识点
需要具备以下java知识可以轻松理解代码
- 建造者模式(最简单的就是lombok的@Builder注解)
- java泛型的使用
- 函数式接口(这里只用到了Function)
- 递归调用
- mapstruct框架的使用
实现步骤
- 一般来说,树形结构通常是给前端渲染的,必要的有4个字段
- 主键、父级id、显示内容 还有一个子节点
- id、parentId、label、children
TreeModel 公共树实体
@Data
@NoArgsConstructor
public class TreeModel<M> {
/**
* 主键
*/
private Long id;
/**
* 父级id
*/
private Long parentId;
/**
* 显示内容
*/
private String label;
/**
* 子节点
*/
private List<M> children;
}
这里我们需要定一个泛型,业务场景不同的实体构建的树不同、参数不同
TreeBuilder 构建树结构类
public class TreeBuilder<Model, TM extends TreeModel<TM>> {
// 假设我们是 menu 菜单实体,那需要转换成 MenuTreeVo 树实体VO类
// Model就是原始实体,TM是继承自TreeModel的实体类,如Menu 与 MenuTreeVo
private List<Model> modelList;
private List<TM> treeList;
// 步骤:
// 1. 构建 设置属性
// 2. 将model属性转换成TM
public static <Model, TM extends TreeModel<TM>> TreeBuilder<Model, TM> build(List<Model> list) {
TreeBuilder<Model, TM> builder = new TreeBuilder<>();
builder.modelList = Optional.ofNullable(list).orElseGet(ArrayList::new);
return builder;
}
/**
* 转换方法
* <p>这个方法的意义</p>
* <p>* 我们可以动态的将实体,转换成我们需要的TreeMode实体类</p>
* <p>* 不同的类转换方式不同</p>
*
* <p>比如菜单实体Menu,菜单名称这个字段是menuName</p>
* <p>部门实体,部门名称是deptName</p>
* <p>他们字段都不相同,我们可以在使用这个方法时灵活的赋值</p>
*
* @param convertFunction
* @return
*/
public TreeBuilder<Model, TM> convert(Function<List<Model>, List<TM>> convertFunction) {
this.treeList = convertFunction.apply(this.modelList);
return this;
}
public List<TM> builder() {
return new ConvertUtil<TM>(this.treeList).convertTree();
}
/**
* 树构建工具类
*/
@AllArgsConstructor
private static class ConvertUtil<TM extends TreeModel<TM>> {
private List<TM> treeList;
/**
* 核心方法
* @return
*/
private List<TM> convertTree() {
List<Long> allIdList = this.treeList.stream().map(TreeModel::getId).distinct().collect(Collectors.toList());
return this.treeList.stream()
// 这个filter是过滤出顶级列表,也就是根节点的元素
.filter(model -> !allIdList.contains(model.getParentId()))
// 为根节点元素,通过递归设置子节点
.peek(model -> recursionList(this.treeList, model))
.collect(Collectors.toList());
}
/**
* 递归设置子节点
* @param list
* @param item
*/
public void recursionList(List<TM> list, TM item) {
// 第一次进入,list为所有元素 item为顶级节点中的每一个元素
// 我们需要设置item中的children属性,为子节点赋值
// 根据item中的id,找到所有parentId与item的id对应的元素,parentId = id
List<TM> childrenList = list.stream().filter(model -> item.getId().equals(model.getParentId())).collect(Collectors.toList());
item.setChildren(childrenList);
for (TM childrenItem : childrenList) {
// 思考一下,最终停止的条件,是不是在元素中找不到对应的子节点,就不进行递归调用了
// 所以在调用前,判断一下 只有子节点数量大于0的时候,才会递归调用,否则方法就出去
if (list.stream().filter(one -> one.getParentId().equals(childrenItem.getId())).count() > 0) {
recursionList(list, childrenItem);
}
}
}
}
}
模拟实现菜单的树形构建
这里模拟实现菜单的树构建,肯定有一个bean对应的菜单表
public class Menu {
private Long id;
private Long parentId;
private String menuName;
private Integer sort;
public Menu() {
}
public Menu(Long id, Long parentId, String menuName, Integer sort) {
this.id = id;
this.parentId = parentId;
this.menuName = menuName;
this.sort = sort;
}
}
- 为了构建树结构,我们需要给他添加一个children属性,原有的属性不需要改表,只需要继承TreeModel
@EqualsAndHashCode(callSuper = true)
@Data
public class MenuTreeVo extends TreeModel<MenuTreeVo> {
private Long id;
private Long parentId;
private String menuName;
private Integer sort;
}
- 再结合mapstruct,让我们免去手动的set属性赋值
@Mapper(builder = @Builder(disableBuilder = true))
public interface MenuConvert {
MenuConvert INSTANCE = Mappers.getMapper(MenuConvert.class);
@Mappings({
@Mapping(target = "label", source = "menuName"),
})
MenuTreeVo convertTree(Menu param);
List<MenuTreeVo> convertTree(List<Menu> param);
}
准备工作都完成了,可以直接使用TreeBuilder
List<Menu> menus = CollUtil.newArrayList(
new Menu(1L, 0L, "首页", 1),
new Menu(2L, 0L, "系统管理", 2),
new Menu(3L, 2L, "角色", 3),
new Menu(4L, 2L, "菜单", 4),
new Menu(5L, 0L, "用户管理", 5),
new Menu(6L, 5L, "系统用户", 6),
new Menu(7L, 5L, "普通用户", 7),
new Menu(8L, 6L, "管理员列表", 8)
);
List<MenuTreeVo> builder = TreeBuilder.<Menu, MenuTreeVo>build(menus)
.convert(MenuConvert.INSTANCE::convertTree)
.builder();
实现效果