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