是遍历树形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
了
/**
* @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 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);
}