封装树形数据操作工具类

This commit is contained in:
qianshijiang
2025-11-20 15:09:08 +08:00
parent 47f710648b
commit 5374b0567b

View File

@@ -0,0 +1,216 @@
package com.zt.plat.framework.common.util.tree;
import com.alibaba.fastjson.JSON;
import com.zt.plat.framework.common.util.object.ObjectUtils;
import lombok.Data;
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
/**
* 树操作方法工具类
*/
public class TreeUtil {
/**
* 将list合成树
*@param list 需要合成树的List
*@param rootCheck 判断E中为根节点的条件x->x.getPId()==-1L,x->x.getParentId()==null,x->x.getParentMenuId()==0
*@param parentCheck 判断E中为父节点条件(x,y)->x.getId().equals(y.getPId())
*@param setSubChildren E中设置下级数据方法Menu::setSubMenus
*@param<E> 泛型实体对象
*@return 合成好的树
*/
public static <E> List<E> makeTree(List<E> list, Predicate<E> rootCheck, BiFunction<E,E,Boolean> parentCheck, BiConsumer<E,List<E>> setSubChildren){
return list.stream().filter(rootCheck).peek(x->setSubChildren.accept(x,makeChildren(x,list,parentCheck,setSubChildren))).collect(Collectors.toList());
}
/**
*将树打平成tree
*@paramtree需要打平的树
*@paramgetSubChildren设置下级数据方法Menu::getSubMenus,x->x.setSubMenus(null)
*@paramsetSubChildren将下级数据置空方法x->x.setSubMenus(null)
*@return打平后的数据
*@param<E>泛型实体对象
*/
public static <E> List<E> flat(List<E> tree, Function<E,List<E>> getSubChildren, Consumer<E> setSubChildren){
List<E> res = new ArrayList<>();
forPostOrder(tree,item->{
setSubChildren.accept(item);
res.add(item);
},getSubChildren);
return res;
}
/**
*前序遍历
*
*@paramtree需要遍历的树
*@paramconsumer遍历后对单个元素的处理方法x->System.out.println(x)、System.out::println打印元素
*@paramsetSubChildren设置下级数据方法Menu::getSubMenus,x->x.setSubMenus(null)
*@param<E>泛型实体对象
*/
public static <E> void forPreOrder(List<E> tree,Consumer<E> consumer,Function<E,List<E>> setSubChildren){
for(E l : tree){
consumer.accept(l);
List<E> es = setSubChildren.apply(l);
if(es != null && es.size() > 0){
forPreOrder(es,consumer,setSubChildren);
}
}
}
/**
*层序遍历
*
*@paramtree需要遍历的树
*@paramconsumer遍历后对单个元素的处理方法x->System.out.println(x)、System.out::println打印元素
*@paramsetSubChildren设置下级数据方法Menu::getSubMenus,x->x.setSubMenus(null)
*@param<E>泛型实体对象
*/
public static <E> void forLevelOrder(List<E> tree,Consumer<E> consumer,Function<E,List<E>> setSubChildren){
Queue<E> queue=new LinkedList<>(tree);
while(!queue.isEmpty()){
E item = queue.poll();
consumer.accept(item);
List<E> childList = setSubChildren.apply(item);
if(childList !=null && !childList.isEmpty()){
queue.addAll(childList);
}
}
}
/**
*后序遍历
*
*@paramtree需要遍历的树
*@paramconsumer遍历后对单个元素的处理方法x->System.out.println(x)、System.out::println打印元素
*@paramsetSubChildren设置下级数据方法Menu::getSubMenus,x->x.setSubMenus(null)
*@param<E>泛型实体对象
*/
public static <E> void forPostOrder(List<E> tree,Consumer<E> consumer,Function<E,List<E>> setSubChildren){
for(E item : tree) {
List<E> childList = setSubChildren.apply(item);
if(childList != null && !childList.isEmpty()){
forPostOrder(childList,consumer,setSubChildren);
}
consumer.accept(item);
}
}
/**
*对树所有子节点按comparator排序
*
*@paramtree需要排序的树
*@paramcomparator排序规则ComparatorComparator.comparing(MenuVo::getRank)按Rank正序,(x,y)->y.getRank().compareTo(x.getRank())按Rank倒序
*@paramgetChildren获取下级数据方法MenuVo::getSubMenus
*@return排序好的树
*@param<E>泛型实体对象
*/
public static <E> List<E> sort(List<E> tree, Comparator<? super E> comparator, Function<E,List<E>> getChildren){
for(E item : tree){
List<E> childList = getChildren.apply(item);
if(childList != null &&! childList.isEmpty()){
sort(childList,comparator,getChildren);
}
}
tree.sort(comparator);
return tree;
}
private 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());
}
/**
* 使用样例
* @param args
*/
public static void main(String[] args) {
MenuVo menu0 = new MenuVo(0L, -1L, "一级菜单", 0);
MenuVo menu1 = new MenuVo(1L, 0L, "二级菜单", 1);
MenuVo menu2 = new MenuVo(2L, 0L, "三级菜单", 2);
MenuVo menu3 = new MenuVo(3L, 1L, "四级菜单", 3);
MenuVo menu4 = new MenuVo(4L, 1L, "五级菜单", 4);
MenuVo menu5 = new MenuVo(5L, 2L, "六级菜单", 5);
MenuVo menu6 = new MenuVo(6L, 2L, "七级菜单", 6);
MenuVo menu7 = new MenuVo(7L, 3L, "八级菜单", 7);
MenuVo menu8 = new MenuVo(8L, 3L, "九级菜单", 8);
MenuVo menu9 = new MenuVo(9L, 4L, "十级菜单", 9);
//基本数据
List<MenuVo> menuList = Arrays.asList(menu0,menu1, menu2,menu3,menu4,menu5,menu6,menu7,menu8,menu9);
//合成树
/**
* 第1个参数List list为我们需要合成树的List如上面Demo中的menuList
* 第2个参数Predicate rootCheck判断为根节点的条件如上面Demo中pId==-1就是根节点
* 第3个参数parentCheck 判断为父节点条件如上面Demo中 id==pId
* 第4个参数setSubChildren设置下级数据方法如上面Demo中Menu::setSubMenus
*/
List<MenuVo> tree= TreeUtil.makeTree(menuList, x->x.getPId()==-1L,(x, y)->x.getId().equals(y.getPId()), MenuVo::setSubMenus);
System.out.println(JSON.toJSONString(tree));
//先序
/**
* 遍历数参数解释:
* tree 需要遍历的树就是makeTree()合
* 成的对象Consumer consumer 遍历后对单个元素的处理方法x-> System.out.println(x)、 postOrder.append(x.getId().toString())
* Function<E, List> getSubChildren,获取下级数据方法如Menu::getSubMenus
*/
StringBuffer preStr = new StringBuffer();
TreeUtil.forPreOrder(tree,x-> preStr.append(x.getId().toString()),MenuVo::getSubMenus);
ObjectUtils.equalsAny("0123456789",preStr.toString());
//层序
StringBuffer levelStr=new StringBuffer();
TreeUtil.forLevelOrder(tree,x-> levelStr.append(x.getId().toString()),MenuVo::getSubMenus);
ObjectUtils.equalsAny("0123456789",levelStr.toString());
//后序
StringBuffer postOrder=new StringBuffer();
TreeUtil.forPostOrder(tree,x-> postOrder.append(x.getId().toString()),MenuVo::getSubMenus);
ObjectUtils.equalsAny("7839415620",postOrder.toString());
// 树平铺
/**
* flat()参数解释:
* tree 需要打平的树,就是makeTree()合成的对象Function<E, List> getSubChildren,
* 获取下级数据方法如Menu::getSubMenusConsumer setSubChildren,
* 设置下级数据方法x->x.setSubMenus(null)
*/
List<MenuVo> flat = TreeUtil.flat(tree, MenuVo::getSubMenus,x->x.setSubMenus(null));
ObjectUtils.equalsAny(flat.size(),menuList.size());
flat.forEach(x -> {
if (x.getSubMenus() != null) {
throw new RuntimeException("树平铺失败");
}
});
// 按rank正序
/**
* sort参数解释
* tree 需要排序的树,就是makeTree()合成的对象Comparator<? super E> comparator
* 排序规则ComparatorComparator.comparing(MenuVo::getRank) 按Rank正序 ,(x,y)->y.getRank().compareTo(x.getRank())按Rank倒序Function<E, List> getChildren
* 获取下级数据方法MenuVo::getSubMenus
*/
List<MenuVo> sortTree= TreeUtil.sort(tree, Comparator.comparing(MenuVo::getRank), MenuVo::getSubMenus);
// 按rank倒序
List<MenuVo> sortTreeReverse = TreeUtil.sort(tree, (x,y)->y.getRank().compareTo(x.getRank()), MenuVo::getSubMenus);
}
@Data
static class MenuVo {
private Long id; // 主键id
private Long pId; // 父级id
private String name; // 菜单名称
private Integer rank = 0; // 排序
private List<MenuVo> subMenus = new ArrayList<>(); // 子菜单
public MenuVo(Long id, Long pId, String name, Integer rank) {
this.id = id;
this.pId = pId;
this.name = name;
this.rank = rank;
}
}
}