Java优雅的实现树形菜单、树形结构

业务场景

需要将集合转换为树形结构的集合,常见的有菜单树、部门树

最后实现的效果

        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();

实现效果

图片.png

代码有需要的话我放在了git仓库嗷~:gitee地址

ps

没事可以来我的破站逛逛~~欢迎大家

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
假设我们有以下的树形菜单结构: ``` - 菜单1 - 子菜单1.1 - 子菜单1.2 - 孙菜单1.2.1 - 孙菜单1.2.2 - 菜单2 - 子菜单2.1 ``` 我们可以定义一个 Menu 类来表示每个菜单项: ```java class Menu { String name; List<Menu> children; public Menu(String name) { this.name = name; children = new ArrayList<>(); } // 添加子菜单 public void addChild(Menu menu) { children.add(menu); } } ``` 接下来,我们可以使用递归的方式来打印整个树形菜单: ```java class TreeMenu { public static void printMenu(Menu menu, int level) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < level; i++) { sb.append("-"); } sb.append(menu.name); System.out.println(sb.toString()); // 递归打印子菜单 for (Menu childMenu : menu.children) { printMenu(childMenu, level + 1); } } } public class Main { public static void main(String[] args) { // 构建树形菜单 Menu menu1 = new Menu("菜单1"); Menu menu11 = new Menu("子菜单1.1"); Menu menu12 = new Menu("子菜单1.2"); Menu menu121 = new Menu("孙菜单1.2.1"); Menu menu122 = new Menu("孙菜单1.2.2"); menu12.addChild(menu121); menu12.addChild(menu122); menu1.addChild(menu11); menu1.addChild(menu12); Menu menu2 = new Menu("菜单2"); Menu menu21 = new Menu("子菜单2.1"); menu2.addChild(menu21); // 打印树形菜单 TreeMenu.printMenu(menu1, 0); TreeMenu.printMenu(menu2, 0); } } ``` 输出结果为: ``` -菜单1 --子菜单1.1 --子菜单1.2 ---孙菜单1.2.1 ---孙菜单1.2.2 -菜单2 --子菜单2.1 ``` 我们通过递归遍历整棵树,按照菜单层级打印每个菜单项的名称,并加上相应的缩进。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值