利用stream流实现递归遍历树形结构

1. 什么是树形结构

下面用一张图片说明:


若依树形递归 java 树形结构递归遍历_若依树形递归 java

在这张表中,每条数据分别有自己的id和parentId,这些数据通过父与子不断连接,形成了一个树结构。

2. 如何通过stream流处理树形结构

我们最终需要的结果是一个树形的json串,如下:


若依树形递归 java 树形结构递归遍历_java_02

话不多说,直接上代码:

这是course_category类,即数据库对应的vo:


若依树形递归 java 树形结构递归遍历_mysql_03

这是CourseCategoryTreeDto类,即我们所需要的dto类:


若依树形递归 java 树形结构递归遍历_List_04

service层代码实现如下:

@Service
@Slf4j
public class CourseCategoryServiceImpl implements CourseCategoryService {

    @Autowired
    private CourseCategoryMapper courseCategoryMapper;

    @Override
    public List<CourseCategoryTreeDto> queryTreeNodes(String id) {
    	// 获取数据库中的数据,这里建议先排序一下,会快很多
        List<CourseCategoryTreeDto> categoryTreeDtos = courseCategoryMapper.selectTreeNodes(id);
        // 调用递归函数获取树形结构,并排除掉表中的根节点:m -> m.getParentid().equals("1")
        List<CourseCategoryTreeDto> courseCategoryTreeDtos =
                categoryTreeDtos.stream()
                        .filter( m -> m.getParentid().equals("1"))
                        .peek(m -> m.setChildrenTreeNodes(getChildrens(m, categoryTreeDtos)))
                        .collect(Collectors.toList());

        return courseCategoryTreeDtos;
    }

    // 递归函数 过滤拿到父节点为当前节点的节点,递归加入到当前节点的子节点中,再收集起来
    private List<CourseCategoryTreeDto> getChildrens(CourseCategoryTreeDto root, List<CourseCategoryTreeDto> all){
        List<CourseCategoryTreeDto> childrens = all.stream()
                .filter( m -> Objects.equals(m.getParentid(), root.getId()))
                // peek方法返回由该流的元素组成的流,并对每个元素执行所提供的 Consumer操作方法
                // peek和foreach在功能上类似,但foreach执行完函数不做其他操作了,peek完还可以继续对stream进行其它操作
            	// 这篇文章解释的比较清晰:
                .peek(m -> m.setChildrenTreeNodes(getChildrens(m, all)))
                .collect(Collectors.toList());
        return childrens;
    }

}

当然,不用stream流我们也可以实现,如下:

@Service
@Slf4j
public class CourseCategoryServiceImpl implements CourseCategoryService {

    @Autowired
    private CourseCategoryMapper courseCategoryMapper;

    @Override
    public List<CourseCategoryTreeDto> queryTreeNodes(String id) {

        // 获得根节点下所有子节点 注意 CourseCategoryTreeDto是CourseCategory的子类
        // 这里获取的List<CourseCategoryTreeDto>实际上只是List<CourseCategory>罢了 并没有构建树形结构 需要下面的处理
        List<CourseCategoryTreeDto> categoryTreeDtos = courseCategoryMapper.selectTreeNodes(id);

        // 定义一个list作为最终返回的数据
        List<CourseCategoryTreeDto> courseCategoryTreeDtos = new ArrayList<>();
        // 为了方便找子节点的父节点,我们定义一个map来存
        HashMap<String, CourseCategoryTreeDto> nodeMap = new HashMap<>();

        // 将数据封装到list中,只包括了根节点的直接下属节点,即实现树形结构
        categoryTreeDtos.stream().forEach(item -> {
            // 存入hashmap中,当前节点的id和信息
            nodeMap.put(item.getId(), item);
            String parentId = item.getParentid();
            // 如果当前节点的的父节点是传过来的节点时,即为根节点的子节点时,加入该节点
            // 因为根节点为0,是定义的一个无关的节点,传过去的数据应该从根节点的叶子节点开始
            if(parentId.equals(id)){
                courseCategoryTreeDtos.add(item);
            }

            // 此处是获取该项item的父节点的ChildrenTreeNodes,将该项加入对应父节点的的ChildrenTreeNodes中
            CourseCategoryTreeDto parentNode = nodeMap.get(parentId);
            if(parentNode != null){
                List childrenTreeNodes = parentNode.getChildrenTreeNodes();
                if(childrenTreeNodes == null){
                    parentNode.setChildrenTreeNodes(new ArrayList<CourseCategoryTreeDto>());
                }
                parentNode.getChildrenTreeNodes().add(item);
            }
        });
    }

}