JAVA中生成菜单树结构常用方法总结

  • Post author:
  • Post category:java




前言

项目开发中经常会遇到树形结构,如多级菜单、多级文件夹结构、多级分类结构、多级组织结构,这些结构都有个共同特点,就是一般存在数据库中是通过id和parentId保存父子级关系的,返回给前端需要合成一颗树,本文针对这类数据结构,总结出常用合成树的三种方法。



方法一:递归合成法

一般在会有一个Menu对象

@Data
public class Menu  {
    public Menu(Integer id, Integer parentId, String name, Integer weight) {
        this.id = id;
        this.parentId = parentId;
        this.name = name;
        this.weight = weight;
    }

    private Integer id;
    private Integer parentId;
    private String name;
    private Integer weight;
    private List<Menu> children;
}
    public static void main(String[] args)  {
        List<Menu> list = new ArrayList<>();
        list.add(new Menu(1, 0, "用户管理", 2));
        list.add(new Menu(2, 0, "租户管理", 1));
        list.add(new Menu(3, 1, "添加用户", 3));
        list.add(new Menu(4, 1, "删除用户", 2));
        list.add(new Menu(5, 2, "添加租户", 1));
        list.add(new Menu(6, 2, "删除租户", 2));
        
        List<Menu> tree1 = buildTree(list);
        System.out.println(JSONUtil.toJsonStr(tree1));
	}
	
    public static List<Menu> buildTree(List<Menu> list) {
        return list.stream()
                .filter(menu -> menu.getParentId() == 0)
                .peek(menu -> menu.setChildren(getChildrens(menu, list)))
                .sorted(Comparator.comparing(Menu::getWeight))
                .collect(Collectors.toList());
    }
    public static List<Menu> getChildrens(Menu root, List<Menu> allMenus) {
        return allMenus.stream()
                .filter(menu -> Objects.equals(menu.getParentId(), root.getId()))
                .peek(menu -> menu.setChildren(getChildrens(menu, allMenus)))
                .sorted(Comparator.comparing(Menu::getWeight))
                .collect(Collectors.toList());
    }



方法二:使用泛型通用合成法


public interface TreeNode<T, E> extends Comparable<E> {
    T getId();
    T getParentId();
    T getWeight();
    boolean isRoot();
    void setChildren(List<? extends TreeNode<T, E>> children);
}

@Data
public class Menu implements TreeNode<Integer, Menu> {
    public Menu(Integer id, Integer parentId, String name, Integer weight) {
        this.id = id;
        this.parentId = parentId;
        this.name = name;
        this.weight = weight;
    }
    private Integer id;
    private Integer parentId;
    private String name;
    private Integer weight;
    private List<Menu> children;
    @Override
    public void setChildren(List menus) {
        this.children = menus;
    }
    @Override
    public boolean isRoot() {
        return this.parentId == 0;
    }
    @Override
    public int compareTo(Menu o) {
        return this.weight.compareTo(o.getWeight());
    }
}

    public static void main(String[] args) {
        List<Menu> list = new ArrayList<>();
        list.add(new Menu(1, 0, "用户管理", 2));
        list.add(new Menu(2, 0, "租户管理", 1));
        list.add(new Menu(3, 1, "添加用户", 3));
        list.add(new Menu(4, 1, "删除用户", 2));
        list.add(new Menu(5, 2, "添加租户", 1));
        list.add(new Menu(6, 2, "删除租户", 2));
        List<Menu> tree2 = tree(list);
        System.out.println(JSONUtil.toJsonStr(tree2));
    }
    public static <E extends TreeNode> List<E> getSubs(E parent, List<E> allData) {
        return allData.stream()
                .filter(x -> Objects.equals(x.getParentId(), parent.getId()))
                .peek(x -> x.setChildren(getSubs(x, allData)))
                .sorted()
                .collect(Collectors.toList());
    }
    public static <E extends TreeNode> List<E> tree(List<E> allData) {
        return allData.stream()
                .filter(TreeNode::isRoot)
                .peek(x -> x.setChildren(getSubs(x, allData)))
                .sorted()
                .collect(Collectors.toList());
    }



方法三:使用Hutool 工具TreeUtill合成法

    public static void main(String[] args) {
        List<Menu> list = new ArrayList<>();
        list.add(new Menu(1, 0, "用户管理", 2));
        list.add(new Menu(2, 0, "租户管理", 1));
        list.add(new Menu(3, 1, "添加用户", 3));
        list.add(new Menu(4, 1, "删除用户", 2));
        list.add(new Menu(5, 2, "添加租户", 1));
        list.add(new Menu(6, 2, "删除租户", 2));


        TreeNodeConfig treeNodeConfig = new TreeNodeConfig();
        treeNodeConfig.setIdKey("id");
        List<Tree<Integer>> tree3 = TreeUtil.build(list, 0, treeNodeConfig, (node, tree) -> {
            tree.setId(node.getId());
            tree.setParentId(node.getParentId());
            tree.setName(node.getName());
            tree.setWeight(node.getWeight());
        });
        System.out.println(JSONUtil.toJsonStr(tree3));
    }



方法四:使用Lambda 表达式改写泛型接口


    public static void main(String[] args) {
        List<Menu> list = new ArrayList<>();
        list.add(new Menu(1, 0, "用户管理", 2));
        list.add(new Menu(2, 0, "租户管理", 1));
        list.add(new Menu(3, 1, "添加用户", 3));
        list.add(new Menu(4, 1, "删除用户", 2));
        list.add(new Menu(5, 2, "添加租户", 1));
        list.add(new Menu(6, 2, "删除租户", 2));
        List<Menu> menus= makeTree(list,x-> x.getParentId()==0,(x,y)-> x.getId().equals(y.getParentId()), Menu::setChildren);
        System.out.println(JSONUtil.toJsonStr(menus));
    }

    public static <E> List<E> makeTree(List<E> list, Predicate<E> root, BiFunction<E,E,Boolean> parentCheck, BiConsumer<E,List<E>> children) {
        return list.stream().filter(root).peek(x->children.accept(x,makeChildren(x,list, parentCheck,children))).collect(Collectors.toList());
    }
    public static <E> List<E> makeChildren(E parent, List<E> allData, BiFunction<E,E,Boolean> parentCheck, BiConsumer<E,List<E>> children) {
        return allData.stream().filter(x-> parentCheck.apply(parent,x)).peek(x->children.accept(x,makeChildren(x,allData, parentCheck,children))).collect(Collectors.toList());
    }




总结

  1. 方法一:

    优点:逻辑简单清晰

    缺点:代码不能复用
  2. 方法二:

    优点:代码可以复用

    缺点:需要实现接口,代码侵入性太强
  3. 方法三:

    优点:无代码侵入,可复用

    缺点:需要引用Hutool包,返回对象变成Hutoo的Tree
  4. 方法四:

    优点:无代码侵入,代码可复用,不需要引用额外包

    缺点:不支持JDK1.8以下版本



版权声明:本文为whzhaochao原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。