1. 创建一个层级菜单常用的类

这个类包含了层级菜单基本的属性

  • id 菜单对应的id
  • parentId 子菜单的父级id
  • name 菜单的名称
  • child 当需要将自己菜单放置到父级菜单里的集合中使用
  • childIds 返回扁平化的菜单使用这种形式
package net.lesscoding.utils;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * @author eleven
 * @date 2022/11/3 16:13
 * @description
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysMenu {
    /** id */
    private Integer id;
    /** 父级id */
    private Integer parentId;
    /** TreeMenuMapUtil 转换扁平map菜单使用此字段*/
    private List<Integer> childIds;
    /** treeMenuUtil转换层级菜单使用此字段 */
    private List<SysMenu> child;
    /** 菜单名称 */
    private String name;
}

2. 数组嵌套形式返回

具体可参考Java 通用树状菜单返回工具类 TreeMenuUtil

package net.lesscoding.utils;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author eleven
 * @date 2022/4/15 10:51
 * @apiNote 返回层级菜单
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class TreeMenuUtil<T> {
    /** 顶层节点的值 */
    private String rootValue;
    /** 对应父节点的子属性 */
    private String childKey;
    /** 父节点的属性值 */
    private String rootKey;
    /** 子菜单放置的字段 */
    private String childProperty;
    /** 要过滤的集合 */
    private List<T> list;

    /**
     * @apiNote 返回树状菜单
     * @return List
     */
    public List<T> rootMenu(){
        //判断rootValue是否为空,如果为空的话给rootValue赋值
        ifNullRootValueSetValue();
        //筛选出来顶级目录
        List<T> rootList = list.stream()
                .filter(item -> StrUtil.equals(rootValue, getValueByProperty(item, childKey)))
                .collect(Collectors.toList());
        //从数据集合中剔除顶层目录,减少后续遍历次数,加快速度
        list.removeAll(rootList);
        //如果list不为空的话则遍历赋值子菜单
        if (CollUtil.isNotEmpty(rootList)) {
            for (T t : rootList) {
                setChildren(t, list);
            }
            return rootList;
        }else{
            return list;
        }
    }

    /**
     * 判断rootKey是控制的话则赋值
     */
    private void ifNullRootValueSetValue(){
        if(StrUtil.isBlank(rootValue)){
            Set<String> rootValueSet = list.stream()
                    .map(m -> getValueByProperty(m, rootKey))
                    .collect(Collectors.toSet());
            Set<String> childValueSet = list.stream()
                    .map(m -> getValueByProperty(m, childKey))
                    .collect(Collectors.toSet());
            //将childKey(parentId)中的数据全部赋值过来
            Set<String> resultList = new HashSet<>();
            resultList.addAll(childValueSet);
            //算出childValueSet和rootValueSet的交集
            childValueSet.retainAll(rootValueSet);
            // 算出childValueSet 与交集不同的部分
            resultList.removeAll(childValueSet);
            //如果有差集的话,把第一个数据返回到rootValue中
            if(CollUtil.isNotEmpty(resultList)){
                rootValue = String.valueOf(resultList.toArray()[0]);
            }
        }
    }

    /**
     * 给父级菜单赋值子菜单
     * @param t         父菜单对象
     * @param list      所有数据
     */
    private void setChildren(T t,List<T> list){
        String childPropertyTypeName = getPropertyDescriptor(t, childProperty).getPropertyType().getName();
        Stream<T> childStream = list.stream()
                .filter(item -> isChild(t, item));
        Collection<T> childList = null;
        if(childPropertyTypeName.contains("Set")){
            childList = childStream.collect(Collectors.toSet());
            setValueByProperty(t, (Set<T>) childList);
        }else {
            childList = childStream.collect(Collectors.toList());
            setValueByProperty(t, (List<T>) childList);
        }
        list.removeAll(childList);
        if (CollUtil.isNotEmpty(childList)) {
            for (T item : childList) {
                setChildren(item, list);
            }
        }
    }

    /**
     * 判断当前对象是不是父级对象的子级
     * @param t     父对象
     * @param item  子对象
     * @return
     */
    private boolean isChild(T t,T item){
        String rootValue = getValueByProperty(t, rootKey);
        String childParentValue = getValueByProperty(item, childKey);
        return rootValue.equals(childParentValue);
    }

    /**
     * 通过属性获取属性的值
     * @param t         对象
     * @param key       属性
     * @return
     */
    private String getValueByProperty(T t,String key){
        PropertyDescriptor keyProperty = getPropertyDescriptor(t,key);;
        try {
            Method keyMethod = keyProperty.getReadMethod();
            return String.valueOf(keyMethod.invoke(t));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 给子级属性赋值
     * @param t         对象
     * @param list      子菜单集合
     */
    private void setValueByProperty(T t,Collection<T> list){
        PropertyDescriptor keyProperty = getPropertyDescriptor(t, childProperty);
        //获取getCurrentPage()方法
        try {
            Method keyMethod = keyProperty.getWriteMethod();
            keyMethod.invoke(t,list);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 通过反射获取中的属性
     * @param t     对象
     * @param key   属性
     * @return
     */
    private PropertyDescriptor getPropertyDescriptor(T t, String key){
        Class clazz = t.getClass();
        try {
            return new PropertyDescriptor(key, clazz);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

}

3. 返回扁平化Map类型的菜单

这种方式 会返回一个map

key 是id childIds里边会放置所有的子集菜单的id
key是root的代表是顶级菜单
思路来源自 我被骂了,但我学会了如何构造高性能的树状结构🔥

package net.lesscoding.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;

/**
 * @author eleven
 * @date 2022/11/3 15:50
 * @description
 */
@Data
@NoArgsConstructor
@Accessors(chain = true)
public class TreeMenuMapUtil<T> {
    /** 顶层节点的值 */
    private String rootValue;
    /** 对应父节点的子属性 */
    private String childKey;
    /** 父节点的属性值 */
    private String rootKey;
    /** 子菜单放置的字段 */
    private String childProperty;
    /** 要过滤的集合 */
    private List<T> list;

    private T t;
    /**
     * 返回扁平化map
     */
    private Map<String,T> hashMap = new HashMap<>();

    public TreeMenuMapUtil<T> rootValue(String rootValue){
        this.rootValue = rootValue;
        return this;
    }

    public TreeMenuMapUtil<T> childKey(String childKey){
        this.childKey = childKey;
        return this;
    }

    public TreeMenuMapUtil<T> rootKey(String rootKey){
        this.rootKey = rootKey;
        return this;
    }

    public TreeMenuMapUtil<T> childProperty(String childProperty){
        this.childProperty = childProperty;
        return this;
    }

    public TreeMenuMapUtil<T> list(List<T> list){
        this.list = list;
        return this;
    }

    public TreeMenuMapUtil<T> instance(T t){
        this.t =  t;
        return this;
    }

    public Map<String,T> rootMenu(Function<? super T, ? extends Object> groupingBy,Function<? super T, ? extends Object> mapFun){
        return rootMenu(t,groupingBy,mapFun);
    }


    /**
     * 转换菜单
     * @param t             泛型对象,用于放置childIds集合
     * @param groupingBy    集合的分组条件
     * @param mapFun        泛型中获取rootKey的lambda表达式
     * @return
     */
    public Map<String,T> rootMenu(T t,Function<? super T, ? extends Object> groupingBy,Function<? super T, ? extends Object> mapFun){
        if(t == null){
            try {
                t = (T)list.get(0).getClass().getDeclaredConstructor().newInstance();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        }
        //判断rootValue是否为空,如果为空的话给rootValue赋值
        ifNullRootValueSetValue();
        //筛选出来顶级目录
        List<T> rootList = list.stream()
                .filter(item -> StrUtil.equals(rootValue, getValueByProperty(item, childKey)))
                .collect(Collectors.toList());
        // 删除顶级节点
        list.removeAll(rootList);
        // 放置 root根节点
        List<String> childIds = getChildIds(rootList, mapFun);
        setValueByProperty(t,childIds);
        hashMap.put("root",t);
        // 将list
        Map<String, List<T>> groupByMap = groupingBy(list, groupingBy);
        list2TreeMap(rootList,groupByMap,mapFun);
        list2TreeMap(list,groupByMap,mapFun);
        return hashMap;
    }

    /**
     * 将list转换成map菜单
     * @param list          要转换的list集合
     * @param groupByMap    已经根据条件分好组的集合
     * @param mapFun        泛型中获取rootKey的lambda表达式
     */
    private void list2TreeMap(List<T> list,Map<String,List<T>> groupByMap,Function<? super T, ? extends Object> mapFun){
        for (T t : list) {
            String valueByProperty = getValueByProperty(t, rootKey);
            List<T> childList = groupByMap.get(valueByProperty);
            if(CollUtil.isNotEmpty(childList)) {
                List<String> childIds = getChildIds(childList, mapFun);
                setValueByProperty(t, childIds);
            }
            hashMap.put(valueByProperty,t);
        }
    }



    private Map<String, List<T>> groupingBy(List<T> list,Function<? super T,? extends Object> groupingBy){
        Map<Object, List<T>> groupByMap = list.stream()
                .collect(Collectors.groupingBy(groupingBy));
        return groupByMap.entrySet().stream()
                .collect(Collectors.toMap(e -> String.valueOf(e.getKey()), Map.Entry::getValue));
    }

    /**
     * 获取 list的id 集合
     * @param list     要获取的集合
     * @param mapFun   获取的lambda表达式
     * @return List   传入一个子集list将子集中的所有键值这里对应的是id 拿出来做一个集合
     */
    private List<String> getChildIds(List<T> list,Function<? super T, ? extends Object> mapFun){
        return list.stream()
                .map(mapFun)
                .distinct()
                .map(String::valueOf)
                .filter(f -> StrUtil.isNotBlank(f))
                .collect(Collectors.toList());
    }

    /**
     * 判断rootKey是控制的话则赋值
     */
    private void ifNullRootValueSetValue(){
        if(StrUtil.isBlank(rootValue)){
            Set<String> rootValueSet = list.stream()
                    .map(m -> getValueByProperty(m, rootKey))
                    .collect(Collectors.toSet());
            Set<String> childValueSet = list.stream()
                    .map(m -> getValueByProperty(m, childKey))
                    .collect(Collectors.toSet());
            //将childKey(parentId)中的数据全部赋值过来
            Set<String> resultList = new HashSet<>();
            resultList.addAll(childValueSet);
            //算出childValueSet和rootValueSet的交集
            childValueSet.retainAll(rootValueSet);
            // 算出childValueSet 与交集不同的部分
            resultList.removeAll(childValueSet);
            //如果有差集的话,把第一个数据返回到rootValue中
            if(CollUtil.isNotEmpty(resultList)){
                rootValue = String.valueOf(resultList.toArray()[0]);
            }
        }
    }


    /**
     * 通过属性获取属性的值
     * @param t         对象
     * @param key       属性
     * @return
     */
    private String getValueByProperty(T t,String key){
        PropertyDescriptor keyProperty = getPropertyDescriptor(t,key);;
        try {
            Method keyMethod = keyProperty.getReadMethod();
            return String.valueOf(keyMethod.invoke(t));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 给子级属性赋值
     * @param t         对象
     * @param list      子菜单集合
     */
    private void setValueByProperty(T t, Collection<?> list){
        PropertyDescriptor keyProperty = getPropertyDescriptor(t, childProperty);
        //获取getCurrentPage()方法
        try {
            Method keyMethod = keyProperty.getWriteMethod();
            keyMethod.invoke(t,list);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 通过反射获取中的属性
     * @param t     对象
     * @param key   属性
     * @return
     */
    private PropertyDescriptor getPropertyDescriptor(T t, String key){
        Class clazz = t.getClass();
        try {
            return new PropertyDescriptor(key, clazz);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

4. 测试

package net.lesscoding.utils;

import com.google.gson.Gson;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * @author eleven
 * @date 2022/11/4 10:11
 * @description
 */
public class TreeUtilTest {
    private static  Gson gson = new Gson();
    public static void main(String[] args) {
        treeMenuMapUtilTest1(initList());
        treeMenuMapUtilTest2(initList());
        treeMenuMapUtilTest3(initList());
        treeMenuUtilTest(initList());
    }

    public static List<SysMenu> initList() {
        SysMenu sysMenu = new SysMenu(1,null,null,null,"1");
        SysMenu sysMenu1 = new SysMenu(2,null,null,null,"1");
        SysMenu sysMenu2 = new SysMenu(3,1,null,null,"1");
        SysMenu sysMenu3 = new SysMenu(4,1,null,null,"1");
        SysMenu sysMenu4 = new SysMenu(5,3,null,null,"1");
        List<SysMenu> list = new ArrayList<>();
        Collections.addAll(list,sysMenu,sysMenu1,sysMenu2,sysMenu3,sysMenu4);
        return list;
    }

    /**
     * 转换成map形式的菜单 调用方式1
     * @param list 数据库查询记录
     */
    public static void treeMenuMapUtilTest1(List<SysMenu> list){
        // 第一种方式调用
        TreeMenuMapUtil<SysMenu> sysMenuTreeMenuMapUtil = new TreeMenuMapUtil<>();
        // 根节点对应的实体类属性
        sysMenuTreeMenuMapUtil.setRootKey("id");
        // 根节点的父级id的值
        sysMenuTreeMenuMapUtil.setRootValue(null);
        // 实体类中对应父节点的属性值
        sysMenuTreeMenuMapUtil.setChildKey("parentId");
        // 存放子菜单的属性
        sysMenuTreeMenuMapUtil.setChildProperty("childIds");
        // 数据库查询到的扁平化的集合数据
        sysMenuTreeMenuMapUtil.setList(list);

        Map<String, SysMenu> stringSysMenuMap = sysMenuTreeMenuMapUtil.rootMenu(new SysMenu(), SysMenu::getParentId, SysMenu::getId);

        System.out.println(gson.toJson(stringSysMenuMap));
    }

    /**
     * 转换成map形式的菜单 调用方式2
     * @param list 数据库查询记录
     */
    public static void treeMenuMapUtilTest2(List<SysMenu> list){

        /**
         * 第二种调用方式
         * 链式调用
         */
        Map<String, SysMenu> utilMap = new TreeMenuMapUtil<SysMenu>()
                .rootKey("id")
                .rootValue(null)
                .childKey("parentId")
                .childProperty("childIds")
                .list(list)
                // 一个对象用来存放map中的root节点
                .instance(new SysMenu())
                .rootMenu(SysMenu::getParentId, SysMenu::getId);
        System.out.println(gson.toJson(utilMap));
    }

    /**
     * 转换成map形式的菜单 调用方式3
     * @param list 数据库查询记录
     */
    public static void treeMenuMapUtilTest3(List<SysMenu> list){
        /**
         * 第三种调用方式
         * 这种方法会根据list中的第一个对象,
         * 获取他的类型,然后调用默认的构造方法创建创建出来一个泛型对象
         * 建议使用第二种方式
         */
        Map<String, SysMenu> utilMap2 = new TreeMenuMapUtil<SysMenu>()
                .rootKey("id")
                .rootValue(null)
                .childKey("parentId")
                .childProperty("childIds")
                .list(list)
                .rootMenu(SysMenu::getParentId, SysMenu::getId);
        System.out.println(gson.toJson(utilMap2));
    }

    /**
     * 数组方式,将子菜单放置到父菜单的child属性中
     * @param list  数据库查询到的数据
     */
    public static void treeMenuUtilTest(List<SysMenu> list){
        List<SysMenu> menuList = new TreeMenuUtil<SysMenu>(null, "parentId", "id", "child", list)
                .rootMenu();
        System.out.println(gson.toJson(menuList));
    }
}
  • 扁平化map返回示例
{
    "1": {
        "id": 1,
        "childIds": [
            "3",
            "4"
        ],
        "name": "1"
    },
    "2": {
        "id": 2,
        "name": "1"
    },
    "3": {
        "id": 3,
        "parentId": 1,
        "childIds": [
            "5"
        ],
        "name": "1"
    },
    "4": {
        "id": 4,
        "parentId": 1,
        "name": "1"
    },
    "5": {
        "id": 5,
        "parentId": 3,
        "name": "1"
    },
    "root": {
        "childIds": [
            "1",
            "2"
        ]
    }
}
  • 数组子集返回示例
[
    {
        "id": 1,
        "child": [
            {
                "id": 3,
                "parentId": 1,
                "child": [
                    {
                        "id": 5,
                        "parentId": 3,
                        "child": [],
                        "name": "1"
                    }
                ],
                "name": "1"
            },
            {
                "id": 4,
                "parentId": 1,
                "child": [],
                "name": "1"
            }
        ],
        "name": "1"
    },
    {
        "id": 2,
        "child": [],
        "name": "1"
    }
]

5. 写到最后

就是自己写着玩,没有考虑到效率之类的问题,仅仅做一个记录作用,如果有大佬有优化方案,可以帮忙看一下哈哈哈😄 🐶保命