stream构建树形菜单

树形菜单在数据库中存放的方式一般是平铺,通过父级id的方式关联的,
查询出来一般也是直接查询出来,结果是一个平铺的list集合。
后端要处理的话一般使用递归的方式遍历查询。
如果后端不处理的话,也可以直接给前端处理,但是最好还是后端处理好了直接给前端。
这里第一种方式演示递归方式创建树形菜单;
这里第二种方式演示递归+stream方式创建树形菜单;

  1. 先创建Tree节点
@Data
public class Tree {

    private int id; //主键

    private String name;   //节点名字

    private int parentId;   //父节点编号

    private List<Tree> childList;

    public Tree(int id, String name, int parentId) {
        this.id = id;
        this.name = name;
        this.parentId = parentId;
    }
}
  1. 再编写获取子节点的方法
/**
 2. parentNode: 父节点
 3. nodes: 所有的节点集合
 */
private static void  findChildNode(Tree parentNode, List<Tree> nodes) {
    for (Tree node : nodes) {
    	//遍历所有的节点,判断其父节点的id是否和传入的节点id相同
        if (node.getParentId() == parentNode.getId()) {
            List<Tree> childNodes = parentNode.getChild();
            if (childNodes == null) {   //判断该节点的子节点列表是否为空
                childNodes = new ArrayList<>();
            }
            childNodes.add(node);
            parentNode.childList(childNodes);
            //递归查询子节点
            findChildNode(node, nodes); 
        }
    }
}
  1. 获取树形菜单
private static Tree getTree(List<Tree> nodes) {
	//设置根节点
    Tree start = null;
    for (Tree node : nodes) {
        int pid = node.getParentId();
        //默认根节点的pid为0
        if (pid == 0) {
            //根节点
            start = node;
            //查询子节点
            findChildNode(start, nodes);    
        }
    }
    return start;
}
  1. 测试
public static void main (String[] args){
    Tree start = new Tree(1, "根节点", 0);
    Tree node1 = new Tree(2, "A节点", 1);
    Tree node2 = new Tree(3, "B节点", );
    Tree node3 = new Tree(4, "C节点", );
    Tree node4 = new Tree(5, "D节点", 1);
    Tree node5 = new Tree(6, "AA节点", 2);
    Tree node6 = new Tree(7, "AB节点", 2);
    Tree node7 = new Tree(8, "AC节点", 2);
    Tree node8 = new Tree(9, "BA节点", 3);
    Tree node9 = new Tree(10, "BB节点", 3);
    Tree node10 = new Tree(11, "BC节点", 3,);
    List<Tree> nodes = new ArrayList<>();
    nodes.add(start);
    nodes.add(node1);
    nodes.add(node2);
    nodes.add(node3);
    nodes.add(node4);
    nodes.add(node5);
    nodes.add(node6);
    nodes.add(node7);
    nodes.add(node8);
    nodes.add(node9);
    nodes.add(node10);
    Tree tree = getTree(nodes);
    System.out.println(tree);
}

第二种方式: 传统的for循环遍历遍历没有问题,但是我们可以尝试使用递归+stream方式优化我们的代码;
树形节点不变,我们将获取子节点的方式变成递归加stream

private static List<Node> findChildNode(Node parentNode, List<Node> nodes) {
    List<Node> childList = nodes.stream()
    		//过滤出parentNode的子节点
    		.filter(item -> Objects.equals(parentNode.getId(), item.getParentId()))
    		//重新映射成一个新的node节点
            .map(item -> {
            	//这里设置当前节点的子节点列表, 子节点列表的获取是通过递归的方式获取的
                item.setChildList(findChildNode(item, nodes));
                return item;
            }).collect(Collectors.toList());
    return childList;
}

测试

public static void main (String[] args){
    List<Node> nodes = Arrays.asList(
        new Node(1, "根节点", 0),
        new Node(2, "根节点1", 1),
        new Node(3, "根节点1.1", 2),
        new Node(4, "根节点1.2", 2),
        new Node(5, "根节点1.3", 2),
        new Node(6, "根节点2", 1),
        new Node(7, "根节点2.1", 6),
        new Node(8, "根节点2.2", 6),
        new Node(9, "根节点2.2.1", 7),
        new Node(10, "根节点2.2.2", 7),
        new Node(11, "根节点3", 1),
        new Node(12, "根节点3.1", 11));
        
        List<Node> tree = nodes.stream()
        	//过滤出parentId为0的节点为根节点 (如果有多个根节点也可以)
        	.filter(item -> item.getParentId() == 0)
        	.map(item -> {
    			item.setChildList(findChildNode(item, nodes));
    			return item;})
    		.collect(Collectors.toList());
    	Node root = treeList.get(0);
		System.out.println(root);
}

总结
这样做的好处是代码更简洁了,而且节省了中间变量,但是这样对于基础弱的人或者不理解这样子递归的人比较有难度,但是就是因为这样,就一定要多写这样的代码,可以加深stream和递归的认识;
第二种方式不理解的,我简单说下思路:
其实可以理解为从最后一层子节点开始遍历,最后一层子节点肯定没有自己的子节点的,调用findChildNode方法肯定返回null,该子节点就获取完毕,继续遍历同级节点,当遍历结束后跳出方法时,返回的是最后一层子节点的集合,再设置到它们的父节点中,再遍历和父节点同级的节点,一层一层遍历,最后返回的就是整棵树。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值