递归方法的实际应用
一、业务场景
项目开发中,一般是不推荐使用递归调用的,因为递归调用很占用内存,并且一个不留神就可能变成死递归,
整个项目可能都会因为这个递归调用而挂掉,造成非常严重的后果。典型案例就是可以在电脑上面递归调用创建
文件夹,会直接损坏电脑上的硬盘。以前亲自见到过好奇心重的人干这事,结果直接让某个磁盘废掉。所以递归的
代码一般都会慎用,能不用就不用。
二、需求分析
今天写的这篇日志主要是针对特殊场景下使用递归来解决问题。比如机构数据,以贵州省的数据为例,下面有
贵阳市,铜仁市;贵阳市下面有南明区,云岩区。铜仁市下面有碧江区,万山区。碧江区下面又有北门社区,环北社区。
机构是分层级的,存在子父级关系,比如父一级的id是子一级的父id。整个数据结构的形式就是一个树形结构。数据
处理的时候,前端需要的是树形结构的数据,便于进行处理。由于数据不多,因此就一次性返给前端。问题是如何
构建这种子父级的关系呢?能够确定的一点就是能够拿到最高一级机构的id,就可以获取到最高机构的数据,然后获取
子机构数据的时候,就需要程序来进行处理。通过循环也能够解决问题,比如通过一级机构贵州省,可以获取到二级机构
贵阳市和铜仁市的数据,然后在分别去获取他们子机构的数据,多轮循环也能解决问题,只是这种处理方式比较麻烦,不优雅。
自己能想到的办法就是采取递归调用的方式进行处理。这是最简单,最便捷的方式,但是一定要确保自己写的代码的正确性。
否则还是使用多轮循环处理好些。
三、解决方案
首先看一个简单的递归调用的例子,网上的各种博文讲得最多的就是求一个数的阶乘,使用递归来做非常方便。
示例代码如下:
public class RecursionTest {
public static void main(String[] args) {
int result = factorial(3);
System.out.println("阶乘计算结果--->" + result);
}
/* @Description: 递归方法
* @author: yilang
* @date: 2022/10/27 11:15
* @param: a
* @return: int
*/
public static int factorial(int a) {
if (a == 1) {
return a;
} else {
return a * factorial(a - 1);
}
}
}
测试结果如下.
设计一张简单的机构表:
CREATE TABLE `gz_organization_code` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`region_code` varchar(32) NOT NULL COMMENT '区域编码',
`parent_region_code` varchar(20) DEFAULT NULL COMMENT '父级编码',
`region_level` tinyint(4) DEFAULT NULL COMMENT '级别',
`region_name` varchar(20) DEFAULT NULL COMMENT '区域名称',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 COMMENT='机构编码表';
插入10条数据,SQL如下.
INSERT INTO `applets`.`gz_organization_code` (`id`, `region_code`, `parent_region_code`, `region_level`, `region_name`) VALUES ('1', '0001', '0', '1', '贵州省');
INSERT INTO `applets`.`gz_organization_code` (`id`, `region_code`, `parent_region_code`, `region_level`, `region_name`) VALUES ('2', '0002', '0001', '2', '贵阳市');
INSERT INTO `applets`.`gz_organization_code` (`id`, `region_code`, `parent_region_code`, `region_level`, `region_name`) VALUES ('3', '0003', '0001', '2', '铜仁市');
INSERT INTO `applets`.`gz_organization_code` (`id`, `region_code`, `parent_region_code`, `region_level`, `region_name`) VALUES ('4', '00021', '0002', '3', '南明区');
INSERT INTO `applets`.`gz_organization_code` (`id`, `region_code`, `parent_region_code`, `region_level`, `region_name`) VALUES ('5', '00022', '0002', '3', '云岩区');
INSERT INTO `applets`.`gz_organization_code` (`id`, `region_code`, `parent_region_code`, `region_level`, `region_name`) VALUES ('6', '00031', '0003', '3', '碧江区');
INSERT INTO `applets`.`gz_organization_code` (`id`, `region_code`, `parent_region_code`, `region_level`, `region_name`) VALUES ('7', '00032', '0003', '3', '万山区');
INSERT INTO `applets`.`gz_organization_code` (`id`, `region_code`, `parent_region_code`, `region_level`, `region_name`) VALUES ('8', '0003101', '00031', '4', '北门社区');
INSERT INTO `applets`.`gz_organization_code` (`id`, `region_code`, `parent_region_code`, `region_level`, `region_name`) VALUES ('9', '0003102', '00031', '4', '环被社区');
INSERT INTO `applets`.`gz_organization_code` (`id`, `region_code`, `parent_region_code`, `region_level`, `region_name`) VALUES ('10', '0003103', '00031', '4', '市中街道');
之后根据这个简单的递归调用方法,来写当前机构子机构处理的代码。经过反复的修改与调试,最终完成代码,
测试调试通过。示例代码如下,
// 一次性查询出全部的数据
@GetMapping("/region")
public List<RegionCodePo> regioncode(){
List<RegionCodePo> regionCodes = this.testMapper.queryGZRegionCodeList();
regionCodes = handlerRegionCodes(regionCodes);
return regionCodes;
}
private List<RegionCodePo> handlerRegionCodes (List<RegionCodePo> regionCodes) {
if (regionCodes == null || regionCodes.size() < 1) {
return Collections.emptyList();
}
RegionCodePo GZSRegionCode = null;
for (RegionCodePo regionCodePo : regionCodes) {
if ("0".equals(regionCodePo.getParentRegionCode())) {
// 获取第一级贵州省的区域编码
GZSRegionCode = regionCodePo;
break;
}
}
handlerChildRegionCodes(regionCodes, GZSRegionCode);
regionCodes.clear();
regionCodes.add(GZSRegionCode);
return regionCodes;
}
// 处理子机构数据
private RegionCodePo handlerChildRegionCodes (List<RegionCodePo> regionCodes, RegionCodePo parentRegionCodePo) {
if (regionCodes == null || regionCodes.size() < 1) {
return parentRegionCodePo;
}
List<RegionCodePo> childRegionCodes = new ArrayList<>();
for (RegionCodePo regionCodePo : regionCodes) {
if (parentRegionCodePo.getRegionCode().equals(regionCodePo.getParentRegionCode())) {
// 有子机构,则递归调用
handlerChildRegionCodes(regionCodes, regionCodePo);
// 添加单条子机构数据
childRegionCodes.add(regionCodePo);
}
}
if (childRegionCodes == null || childRegionCodes.size() < 1) {
return parentRegionCodePo;
}
// 添加子机构数据
parentRegionCodePo.setChildRegionCodes(childRegionCodes);
return parentRegionCodePo;
}
/**
* @Author 一只爱阅读的程序员
* @Description 区域代码
* @Date 2022/10/27 0:06
* @Version 1.0
*/
@Data
public class RegionCodePo {
/*
* id
*/
private Integer id;
/*
* 区域编码
*/
private String regionCode;
/*
* 区域父编码
*/
private String parentRegionCode;
/*
* 区域级别
*/
private String regionLevel;
/*
* 区域名称
*/
private String regionName;
/*
* 子机构
*/
private List<RegionCodePo> childRegionCodes;
}
返回结果如下.
[{"id":1,"regionCode":"0001","parentRegionCode":"0","regionLevel":"1","regionName":"贵州省","childRegionCodes":[{"id":2,"regionCode":"0002","parentRegionCode":"0001","regionLevel":"2","regionName":"贵阳市","childRegionCodes":[{"id":4,"regionCode":"00021","parentRegionCode":"0002","regionLevel":"3","regionName":"南明区","childRegionCodes":null},{"id":5,"regionCode":"00022","parentRegionCode":"0002","regionLevel":"3","regionName":"云岩区","childRegionCodes":null}]},{"id":3,"regionCode":"0003","parentRegionCode":"0001","regionLevel":"2","regionName":"铜仁市","childRegionCodes":[{"id":6,"regionCode":"00031","parentRegionCode":"0003","regionLevel":"3","regionName":"碧江区","childRegionCodes":[{"id":8,"regionCode":"0003101","parentRegionCode":"00031","regionLevel":"4","regionName":"北门社区","childRegionCodes":null},{"id":9,"regionCode":"0003102","parentRegionCode":"00031","regionLevel":"4","regionName":"环被社区","childRegionCodes":null},{"id":10,"regionCode":"0003103","parentRegionCode":"00031","regionLevel":"4","regionName":"市中街道","childRegionCodes":null}]},{"id":7,"regionCode":"00032","parentRegionCode":"0003","regionLevel":"3","regionName":"万山区","childRegionCodes":null}]}]}]
然后将这些数据转换为按照指定的层级关系展示如下图,可以去这个网址进行处理 https://jsoncrack.com/editor
写递归代码的注意事项:1.方法一定要有出口,否则就是死递归;.2.方法自己调用自己。.3.一般用于这种存在
子父级关系的数据进行处理,五级以内。如果是层级非常多的情况,则可以考虑使用两个接口来实现这个功能,一个是查询
前面几级机构数据的接口;一个是根据父机构编码查询子机构编码的接口,通过这种方式也能够实现功能。有更好建议的小伙伴,
欢迎留言讨论。