Bootstrap

又更新了一个list转树状结构的工具类

之前写的TreeMenuUtil,之前用反射写的,这次稍微换了换,总的来说还是换汤不换药吧

1. TreeUtil

内部判断是否属于root节点使用的是stream().filter()

1. 工具类

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

import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author eleven
 * @date 2025/1/7 14:47
 * @apiNote 工具类用于将包含父子关系信息的扁平列表转换为树形结构
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class TreeUtil<T> {
    /**
     * 要转化成树状结构的集合
     */
    private List<T> list;

    /**
     * 用于从T类型对象中提取标识节点自身的id的函数,
     */
    private Function<T, ?> idGetter;
    /**
     * 用于从T类型对象中提取标识父节点的parentId的函数
     */
    private Function<T, ?> parentIdGetter;
    /**
     * 用于筛选根节点的条件谓词
     */
    private Predicate<? super T> rootNodeFilter;
    /**
     * 用于设置子节点的方法名
     */
    private String childrenSetter;


    /**
     * 将扁平列表转换为树形结构的主方法
     *
     * @return 转换后的树形结构的根节点列表
     */
    public List<T> list2Tree() {
        // 先找出所有的根节点
        List<T> rootNode = rootNode(list, rootNodeFilter);
        list.removeAll(rootNode);
        // 遍历根节点,为每个根节点设置子节点,构建完整树形结构
        rootNode.forEach(root -> setChildren(root, list));
        return rootNode;
    }

    private void setChildren(T root, List<T> list) {
        List<T> children = list.stream()
                .filter(node -> parentIdGetter.apply(node).equals(idGetter.apply(root)))
                .collect(Collectors.toList());
        if (CollUtil.isNotEmpty(children)) {
            list.removeAll(children);
            try {
                root.getClass()
                        .getMethod(childrenSetter, List.class)
                        .invoke(root, children);
            } catch (Exception e) {
                e.printStackTrace();
            }
            children.forEach(child -> setChildren(child, list));
        }

    }

    /**
     * 根据条件筛选出根节点列表
     *
     * @param list      包含所有节点的扁平列表
     * @param predicate 用于筛选根节点的条件谓词
     * @return 根节点列表
     */
    private static <T> List<T> rootNode(List<T> list, Predicate<? super T> predicate) {
        return list.stream()
                .filter(predicate)
                .collect(Collectors.toList());
    }
}

2. 使用

List<CollectCategory> list = categoryMapper.list(category);
return new TreeUtil<CollectCategory>()
        // 需要转换的集合
        .setList(list)
        // 获取id的方法
        .setIdGetter(CollectCategory::getId)
        // 获取parentId的方法
        .setParentIdGetter(CollectCategory::getParentId)
        // 筛选顶层节点的filter
        .setRootNodeFilter(item -> StrUtil.equalsIgnoreCase("0", item.getParentId()))
        // 设置chidren属性的方法名
        .setChildrenSetter("setChildren")
        // 转换方法
        .list2Tree();

2. TreeMenuUtil

第一次使用的时候按照parentId直接分组,不用每次都遍历list了,使用的是stream().collect(Collectors.groupingBy)

1. 工具类

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

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author eleven
 * @date 2025/1/7 16:50
 * @apiNote
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class TreeMenuUtil<T> {
    /**
     * 要转化成树状结构的集合
     */
    private List<T> list;

    /**
     * 用于从T类型对象中提取标识节点自身的id的函数,
     */
    private Function<T, ?> idGetter;
    /**
     * 用于从T类型对象中提取标识父节点的parentId的函数
     */
    private Function<T, ?> parentIdGetter;
    /**
     * 用于筛选根节点的条件谓词
     */
    private Predicate<? super T> rootNodeFilter;
    /**
     * 用于设置子节点的方法名
     */
    private String childrenSetter;

    private Object rootParentId;

    public List<T> list2Tree() {
        List<T> rootNodes = new ArrayList<>();
        if (rootParentId == null) {
            rootNodes = list.stream()
                    .filter(item -> Objects.isNull(parentIdGetter.apply(item)))
                    .collect(Collectors.toList());
            list.removeAll(rootNodes);
        }
        Map<?, List<T>> listMap = list.stream()
                .collect(Collectors.groupingBy(parentIdGetter));
        if (CollUtil.isEmpty(rootNodes)) {
            rootNodes = listMap.get(rootParentId);
        }
        
        if (CollUtil.isEmpty(rootNodes)) {
            // 自行处理一下没有根节点的情况,这里直接返回原数组了
            return list;
        }

        rootNodes.forEach(root -> setChildren(root, listMap));
        return rootNodes;
    }

    /**
     * @param parent  根节点
     * @param listMap 包含所有节点的Map
     */
    private void setChildren(T parent, Map<?, List<T>> listMap) {
        Object parentId = idGetter.apply(parent);
        List<T> children = listMap.get(parentId);
        if (CollUtil.isNotEmpty(children)) {
            try {
                parent.getClass()
                        .getMethod(childrenSetter, List.class)
                        .invoke(parent, children);
            } catch (Exception e) {
                e.printStackTrace();
            }
            children.forEach(child -> setChildren(child, listMap));
        }
    }
}

2. 使用

return new TreeMenuUtil<CollectCategory>()
        // 要处理的list
        .setList(list)
        // 获取id的Function
        .setIdGetter(CollectCategory::getId)
        // 获取parentId的Function
        .setParentIdGetter(CollectCategory::getParentId)
        // 根节点的parentId, 可以为null
        .setRootParentId(0)
        // 设置子节点的方法名
        .setChildrenSetter("setChildren")
        // 筛选根节点的条件
        .list2Tree();

3. 用@Builder改造了一下,使用起来好像更简单了点

1. @Builder标注

import cn.hutool.core.collection.CollUtil;
import lombok.Builder;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author eleven
 * @date 2025/1/7 16:50
 * @apiNote
 */
@Builder
public class TreeMenuUtil<T> {
    /**
     * 要转化成树状结构的集合
     */
    private List<T> list;

    /**
     * 用于从T类型对象中提取标识节点自身的id的函数,
     */
    private Function<T, ?> idGetter;
    /**
     * 用于从T类型对象中提取标识父节点的parentId的函数
     */
    private Function<T, ?> parentIdGetter;
    /**
     * 用于筛选根节点的条件谓词
     */
    private Predicate<? super T> rootNodeFilter;
    /**
     * 用于设置子节点的方法名
     */
    private String childrenSetter;

    private Object rootParentId;

    public List<T> list2Tree() {
        List<T> rootNodes = new ArrayList<>();
        if (rootParentId == null) {
            rootNodes = list.stream()
                    .filter(item -> Objects.isNull(parentIdGetter.apply(item)))
                    .collect(Collectors.toList());
        }
        Map<?, List<T>> listMap = list.stream()
                .filter(item -> !Objects.isNull(parentIdGetter.apply(item)))
                .collect(Collectors.groupingBy(parentIdGetter));
        if (CollUtil.isEmpty(rootNodes)) {
            rootNodes = listMap.get(rootParentId);
        }
        if (CollUtil.isEmpty(rootNodes)) {
            // 自行处理一下没有根节点的情况,这里直接返回原数组了
            return list;
        }
        rootNodes.forEach(root -> setChildren(root, listMap));
        return rootNodes;
    }

    /**
     * @param parent  根节点
     * @param listMap 包含所有节点的Map
     */
    private void setChildren(T parent, Map<?, List<T>> listMap) {
        Object parentId = idGetter.apply(parent);
        List<T> children = listMap.get(parentId);
        if (CollUtil.isNotEmpty(children)) {
            try {
                parent.getClass()
                        .getMethod(childrenSetter, List.class)
                        .invoke(parent, children);
            } catch (Exception e) {
                e.printStackTrace();
            }
            children.forEach(child -> setChildren(child, listMap));
        }
    }
}

2. 使用

说实话,这个builder的泛型是这么加的我是真没想到

return TreeMenuUtil.<CollectCategory>builder()
        .list(list)
        .idGetter(CollectCategory::getId)
        .parentIdGetter(CollectCategory::getParentId)
        .rootParentId(category.getRootParentId())
        .childrenSetter("setChildren")
        .build()
        .list2Tree();
;