java递归遍历树形list

是遍历树形List,不是生成。当然,因为子节点个数的不确定性,所以,不论是生成树还是遍历树,都得用到递归

网上查了一圈,基本都是生成,不是遍历一棵树形List。而且CSDN有些都是错误的。
比如;
java递归遍历树结构目录
坑啊。

自己写一个了:

应用场景:
项目中有一棵组织树存在redis了,现在要给每个组织增加一些补充信息(比如,给某一个组织加一个额外的审核人)。
那么,有两种做法;
一、改产生这棵树的源代码(并且,这棵树是全状态的,而我只要其中“启用”状态的数据),添加上这些属性(审核人)。但是,有一个弊端:我在我的页面配置一次某组织的审核人,那么这棵redis里的树就得更新一次,否则没有查询条件时走缓存的树,而这棵树永远不变。那么,组织大概有100个左右,生成一棵树大概要10秒,所以,人事妹妹在给组织配审核人时不得痛苦的一批,100*10/60≈20分钟。用户体验非常不好
二、我不动这棵树,“复制”这棵树,筛出要的数据,做一棵新树。再补上要添加的字段


因此,需要实现: 遍历树形list

对树形list(比如系统菜单、公司组织结构)的遍历,每个节点都不确定子节点个数,只能用递归(非要for套for套for也行。只能说天然最适合递归、迭代器配合while做,而且代码更优雅、看起来好看/可读性更高,思路更简捷。)(目前觉得递归是无奈之举[我才不会说我是按for的思路写不下去了啊],是顺着思路,就是只能这么做下去了。应用场景:不合适用for。好处:优雅,简略[减少代码量])
(ps.如果List中元素是平行关系的,则不必要“迭代器配合while”,用它的原因是:需要它的remove(),处理平行关系(旧List,处理一个元素remove一个)重构收缩成一棵树(新List)),或者,对于树形List的遍历需要。
注:递归和while要注意两个都是容易写出死循环的东西,稍不注意,就报错java.lang.StackOverflowError


测试环境树形List数据结构:
在这里插入图片描述

 /**
     * @author Marder
     * 遍历树形List,添加一些补充信息
     * @param org    开始节点    遍历完之后,org变成新的树
     * @param orgs   该节点的子节点集合
     * 因为集合是引用数据类型(传递引用地址) 这里void即可,原来的集合是本身就会改变的
     */
    private void getTree(EhrOrganizationEntity org ,List<EhrOrganizationEntity> orgs){
        //开始节点  id作为下一个节点的 父id
        int parentId = org.getOrId();
        //子集合
        List<EhrOrganizationEntity> childs = new ArrayList<>();
        //创建集合的迭代器。循环时list的remove会抛异常,故必须用迭代器遍历,调它的remove
        Iterator<EhrOrganizationEntity> iterator = orgs.iterator();
        //开始遍历集合
        while (iterator.hasNext()) {
            //集合中的一个元素
            EhrOrganizationEntity ehrOrganizationEntity=iterator.next();
            //该节点的子节点
            List<EhrOrganizationEntity> nextChilds=ehrOrganizationEntity.getList();
            // 公司组织初始节点的parentId是null
            if(ehrOrganizationEntity.getParentId()==null || ehrOrganizationEntity.getParentId() == parentId){
                //子节点
                EhrOrganizationEntity newEntity=new EhrOrganizationEntity();
                newEntity.setOrId(ehrOrganizationEntity.getOrId());
                newEntity.setRoName(ehrOrganizationEntity.getRoName());
                newEntity.setStatus(ehrOrganizationEntity.getStatus());
                newEntity.setParentId(ehrOrganizationEntity.getParentId());
                //该(子)节点的子节点  在EhrOrganizationEntity这个实体类中叫list属性-_-||
                newEntity.setList(nextChilds);
                //添加元素(只需要“启用”的)
                if(EhrCheckworkAssistanapproverController.ORG_START_STATUS.equals(newEntity.getStatus())){
                    childs.add(newEntity);
                }
            }
        }
        //设置子节点集合
        org.setList(childs);

        //给子节点设置它的子节点
        //当子节点不为空时
        if(!CollectionUtils.isEmpty(childs) ){
            //为子节点添加子节点
            Iterator<EhrOrganizationEntity> iterator2 = childs.iterator();
            while(iterator2.hasNext()){
                EhrOrganizationEntity next = iterator2.next();
                //且子节点的子节点集合不为空时,递归
                if(!CollectionUtils.isEmpty(next.getList())){
                    getTree(next,next.getList());
                }

            }
        }
    }

我认为上述递归方法的本质:getTree/又可叫getChildTree

调用示范:

     //redis中取的。
        List<EhrOrganizationEntity> treeList= getOrgTreeList(params);
        //开始节点
        EhrOrganizationEntity firstOrg=ehrOrganizationDao.getOrgTree().get(0);
     
        //递归遍历树,添加信息
        getTree(firstOrg,treeList.get(0).getList());
    
       return R.ok().put("treeList", firstOrg);
   

Ps.
整了一个,感觉递归巨装比,以后都不想写for了。
“递归”在算法的快排中也有。感觉和for相比,前者是高中生😃,后者是小学生玩的😃

Ps.
其实,按这个递归思路,从0开始生成一棵树也是一样的。
法1.写一个查询子列表的接口,从而getChildList()。但这样频繁访问数据库,效率差,比如公司系统里查出这棵树要4~10秒左右。
法2.一次查出所有。再通过stream流筛出parentId等于它orId的集合,得出子列表,从而getChildList()。效率高,思路更简捷。
抽象封装、思路简洁直接——递归很适合计算机啊。

再来一个今天写的最外层元素是平行关系(不存在一个最顶层bean,没有收敛到一个元素上去[其实最外层有没有收敛到一个bean是无所谓的],而最外面是一堆平行元素,每个子元素是树状)子元素是树状的时候这个List用到递归(不需要“迭代器配合while”了):

  /**
     * roName处理成有层级关系的名称
     *
     * 不写私有方法了,写service层,方便给别人(“组织”模块)也调用一下
     * @param orgs   传入要找补全层级关系名称的 List<EhrOrganizationEntity> orgs  (平行结构,不是树形)
     */
    @Override
    public void getOrgParentName4Tree(List<EhrOrganizationEntity> orgs) {
        //查询出所有的组织
        List<EhrOrganizationEntity>  allOrgList=ehrOrganizationService.selectList(new EntityWrapper<>());

        //树的起始节点(名字递归时结束节点)
        EhrOrganizationEntity firstOrg = ehrOrganizationDao.getOrgTree().get(0);
        Integer endId=firstOrg.getParentId();
        if(endId==null){
            //给个默认值,减少递归方法里判断条件的蛋疼
            endId=0;
        }
        if(!CollectionUtils.isEmpty(orgs)){
            //遍历,拼上全名
            for(EhrOrganizationEntity ehrOrganizationEntity:orgs){
                //递归
                List<String> parentNames=new ArrayList<>();
                getParentName(ehrOrganizationEntity,allOrgList,parentNames,endId);
                //得到全名
                Collections.reverse(parentNames);
                String str = parentNames.stream().collect(Collectors.joining("/"));
                ehrOrganizationEntity.setRoName(str);
            }
        }

    }

    /**
     *组织递归得到它的所有父节点的名字
     */
    private void getParentName(EhrOrganizationEntity org, List<EhrOrganizationEntity> allorgs,List<String> parentNames,Integer endId) {
        //父id
        Integer parentId = org.getParentId();
        //产品经理说起点节点的名字不要拼进去
        if(parentId!=null||!parentId.equals(endId)){
            parentNames.add(org.getRoName());
        }
        //strem流挑选出id=父id的集合。   这里对于filter()中orId字段缺少非空判断,懒得弄了,默认正式库没有脏数据
        List<EhrOrganizationEntity> EhrOrganizationEntityList =allorgs.stream().filter(x -> x.getOrId().equals(parentId)).collect(Collectors.toList());
        EhrOrganizationEntity parentEhrOrganizationEntity =null;
        if(!CollectionUtils.isEmpty(EhrOrganizationEntityList)){
            parentEhrOrganizationEntity=EhrOrganizationEntityList.get(0);
        }
        //开始遍历集合
        if(parentEhrOrganizationEntity!=null){
            //注意,这里不能用while,会死循环
            if(parentEhrOrganizationEntity.getParentId()!=null && !parentEhrOrganizationEntity.getParentId().equals(endId)) {
                getParentName(parentEhrOrganizationEntity,allorgs,parentNames,endId);
            }
        }

    }

点评一下上述代码设计:全局变量,局部变量,完美利用引用数据类型的特性(故方法返回void即可已有作用)。属实java基础扎实。

===================2022年9月9日简单递归模板=
有一堆组织,需要把组织集团全名拼出来,比如C部门全名X/Y/C

      List<String> parentNames=new ArrayList<>();
            findParent(wxCpDepart,cpDeptList,parentNames);

 Collections.reverse(parentNames);
        String name =parentNames.stream().collect(Collectors.joining("/"));
-------------------------------------------------


public void findParent(WxCpDepart cpDepart,List<WxCpDepart> cpDeptList,List<String> parentNames){
          if(cpDepart.getId()==Constant.CP_COWAIN){
               return ;
          }
        parentNames.add(cpDepart.getName());
        WxCpDepart parentWxCpDepart = cpDeptList.stream().filter(e -> e.getId().equals(cpDepart.getParentId())).findFirst().orElse(null);
        findParent(parentWxCpDepart,cpDeptList,parentNames);
    }
/** * 根据等级查询类目树 * * @param level * @return */ @Override public List queryCategoryTree(Integer level) { //查询当前级别下类目 List list = categoryDAO.list(level); //组装好的类目树,返回前端 List categoryTree = new ArrayList(); //所有类目 List allDTOList = new ArrayList(); if (CollectionUtils.isEmpty(list)) { return categoryTree; } for (CategoryDO categoryDO : list) { allDTOList.add(new CategoryTreeDTO().convertDOToDTO(categoryDO)); } //当前等级类目 categoryTree = allDTOList.stream().filter(dto -> level.equals(dto.getLevel())).collect(Collectors.toList()); for (CategoryTreeDTO categoryTreeDTO : categoryTree) { //组装类目为树结构 assembleTree(categoryTreeDTO, allDTOList,Constants.CATEGORY_MAX_LEVEL - level); } return categoryTree; } /** * 组装树 * * @param categoryTreeDTO * @param allList * @param remainRecursionCount 剩余递归次数 * @return */ public CategoryTreeDTO assembleTree(CategoryTreeDTO categoryTreeDTO, List allList, int remainRecursionCount) { remainRecursionCount--; //最大递归次数不超过Constants.CATEGORY_MAX_LEVEL-level次,防止坏数据死循环 if(remainRecursionCount < 0){ return categoryTreeDTO; } String categoryCode = categoryTreeDTO.getCategoryCode(); Integer level = categoryTreeDTO.getLevel(); //到达最后等级树返回 if (Constants.CATEGORY_MAX_LEVEL == level) { return categoryTreeDTO; } //子类目 List child = allList.stream().filter(a -> categoryCode.equals(a.getParentCode())).collect(Collectors.toList()); if (null == child) { return categoryTreeDTO; } categoryTreeDTO.setChildren(child); //组装子类目 for (CategoryTreeDTO dto : child) { assembleTree(dto, allList,remainRecursionCount); } return categoryTreeDTO; }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值