【进阶篇】Java 项目中对使用递归的理解分享
【进阶篇】Java 项目中对使用递归的理解分享
前言
笔者在最近的项目开发中,遇到了两个父子关系紧密相关的场景:评论树结构、部门树结构。具体的需求如:找出某条评论下的所有子评论id集合,找出某个部门下所有的子部门id集合。
在之前的项目开发经验中,递归使用得是较少的,但作为一个在数据结构操作中遍历树节点的解决方案,我还是拿出来作为技术积累进行记录以及分享。
一、什么是递归
1.1基本概念
这里就有必要简单介绍一下关于递归的基本概念了。
在 Java 中,递归是指在方法的定义中调用自身的过程,递归是基于方法调用栈的原理实现的:当一个方法被调用时,会在调用栈中创建一个对应的栈帧,包含方法的参数、局部变量和返回地址等信息。在递归中,方法会在自身的定义中调用自身,这会导致多个相同方法的栈帧依次入栈。当满足终止条件时,递归开始回溯,栈帧依次出栈,方法得以执行完毕。
递归的关键是定义好递归的终止条件和递归调用的条件。如果没有适当的终止条件或递归调用的条件不满足,递归可能会陷入无限循环,导致栈内存溢出。
1.2优缺点
优点:
- 简化问题:递归能够将复杂问题分解成更小规模的子问题,简化了问题的解决过程;
- 实现高效算法:递归在某些算法中能够实现高效的解决方法,如数据结构操作中遍历树节点等。
缺点:
- 栈溢出风险:递归可能导致方法调用栈过深,造成栈内存溢出;
- 性能损耗:递归调用需要创建多个栈帧,对系统资源有一定的消耗;
- 可读性不高:递归的使用需要谨慎,不合理地使用可能造成代码难以理解和调试。
1.3与迭代的区别
迭代(Iteration)
迭代常见于 for 循环中:比如有一个集合 A,对 A 进行 foreach,在内部设置条件,符合条件后将集合中某个元素的值替换成别的值。
迭代示例简图
@Test
public void iterationTest(){
ArrayList<String> list = new ArrayList<>();
list.add("计算机技术");
list.add("土木工程");
list.add("市场营销");
list.forEach(val -> {
if (val.contains("计算机")){
log.info("迭代前的的专业名称:{}", val);
String str = val.replace(val, "计算机科学与技术");
log.info("迭代后的的专业名称:{}", str);
}
});
}
结果为:
迭代结果简图
递归(Recursion)
递归的例子会在下一小节详细给出。
二、实际案例
下面笔者以递归获取某个评论id下面所有的子级评论id为例子,向大家介绍这个递归的过程。
首先,这里给出一个简单的数据库评论表的 demo,id 是主键id 也是评论唯一 id,parent_id 是该条评论的父评论 id,status 为1表示审核通过的状态。
其中,我们可以简单发现:这里21为第一层,28和29为第二层、31和32为第三层,草图如下所示:
评论id简单层级示意图
那么,我们如何将21、28、29、31、32都放进一个集合里返回呢?下面的代码示例可以给你一个参考。
但是,在看代码之前,有个问题请你思考一下:
从21开始后,遍历的路线是21-28-29?还是21-28-31?还是21-29-32?或者是21-28-31-29-32?
下面是经过脱敏处理后的参看代码示例,注释都写得比较清楚了:
/**
* 这里可以看作是外部接口的调用,会得到递归的结果
* @param id
*/
private List<Integer> getIdListMethod(Integer id){
ArrayList<Integer> idList = new ArrayList<>();
this.getAllIdByRecursion(id, idList);
log.info("递归后得到的id集合:{}", idList);
return idList;
}
/**
* 这里是递归的过程
* @param id
* @param idList
*/
private void getAllIdByRecursion(Integer id, List<Integer> idList){
LambdaQueryWrapper<Comment> wrapper = new LambdaQueryWrapper<>();
//先把该id下所有的第一级子id找到
wrapper.eq(Comment::getParentId, id).eq(Comment::getStatus, NumberUtils.INTEGER_ONE);
List<Comment> commentList = this.list(wrapper);
for (Comment children : commentList){
this.getAllIdByRecursion(children.getId(), idList);
}
log.info("放入集合的id为:{}", id);
idList.add(id);
}
上面问题的答案是:递归后得到的id集合:[21,28,31,29,32],原因就是:迭代会从一棵树开始遍历到底,没有元素了再从头开始遍历,依次迭代,类似于深度优先遍历。
比如:21下面有两个子id:28和29,那么会先走21-28-31这棵树,到底了后接着按照29-32遍历。
三、改进方案
我根据自己的开发经验,可以从控制递归层数和改用 Stream 这两种办法来对递归进行改进。
3.1控制递归层数
JVM 默认控制的递归最大深度限制在 1000 层,可以通过设置 JVM 参数来控制其深度,如:
java -Xss5m #表示将每个线程的栈内存大小设置为5MB,已经是比较大了
或者在代码层面对递归的层数进行控制:
int depth = 0;
//递归方法调用
for (int i = 0; i < 20; i++) {
depth++;
}
if (depth > 100){
//其它操作
}
3.1用 Stream 遍历
核心思路是:先数据库全量查询(10万条以内),内存中使用 Stream 流操作、Lambda 表达式、Java 地址引用进行筛选。
适用于数据总量不多的情况,如:部门树,部门数量一般情况是比较固定的,一个组织或者公司最多也就几百上千个部门。
详情可以看我这篇文章:https://www.cnblogs.com/CodeBlogMan/p/17965824
四、文章小结
笔者确实不推荐在项目中过度使用递归,但是合理使用的话也能成为解决特定问题的一个利器,至于怎么拿捏这个度,那就要看大家的具体情况了。
Java 项目中对使用递归的理解分享到这里就结束了,文章如有不足和错误,或者你有更好的解决思路,欢迎大家的指正和交流!
【进阶篇】Java 项目中对使用递归的理解分享的更多相关文章
- JAVA项目中常用的异常处理情况总结
JAVA项目中常用的异常知识点总结 1. java.lang.nullpointerexception这个异常大家肯定都经常遇到,异常的解释是"程序遇上了空指针",简单地说就是调用 ...
- JAVA项目中常用的异常知识点总结
JAVA项目中常用的异常知识点总结 1. java.lang.nullpointerexception这个异常大家肯定都经常遇到,异常的解释是"程序遇上了空指针",简单地说就是调用 ...
- 在Java项目中整合Scala
Scala是一个运行在Java JVM上的面向对象的语言.它支持函数编程,在语法上比Java更加灵活,同时通过Akka库,Scala支持强大的基于Actor的多线程编程.具有这些优势,使得我最近很想在 ...
- redis在java项目中的使用
在上一篇文章中已经讲了redis的spring配置,这篇将会描述redis在java项目中的使用. redis存储形式都是key-value(键值对),按照存储的内容分为两种,一种是存简单数据,即数字 ...
- Mac笔记本中是用Idea开发工具在Java项目中调用python脚本遇到的环境变量问题解决
问题描述: mac笔记本本身会自带几个python版本,比如python2.7版本,我没有改动mac默认的python版本,只是安装了python3.7版本. 使用Pycharm开发Python项目没 ...
- JAVA项目中公布WebService服务——简单实例
1.在Java项目中公布一个WebService服务: 怎样公布? --JDK1.6中JAX-WS规范定义了怎样公布一个WebService服务. (1)用jdk1.6.0_21以后的版本号公布. ( ...
- 实战派 | Java项目中玩转Redis6.0客户端缓存!
原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 哈喽大家好啊,我是Hydra. 在前面的文章中,我们介绍了Redis6.0中的新特性客户端缓存client-side caching,通过tel ...
- eclipse java项目中明明引入了jar包 为什么项目启动的时候不能找到jar包 项目中已经 引入了 com.branchitech.app 包 ,但时tomcat启动的时候还是报错? java.lang.ClassNotFoundException: com.branchitech.app.startup.AppStartupContextListener java.lang.ClassN
eclipse java项目中明明引入了jar包 为什么项目启动的时候不能找到jar包 项目中已经 引入了 com.branchitech.app 包 ,但时tomcat启动的时候还是报错?java. ...
- java项目中build path的设置
右键点击项目新建文件libs 添加jtds jar包引用本地动态链接库(dll)的设置方法 配置LibraryJRE的添加和更换 Java项目中build path的设置总结,包括JRE的添加和更 ...
- XML在JAVA项目中的作用
java项目中,xml文件一般都是用来存储一些配置信息 一般的编程, 多数用来存储配置信息 . 拿JDBC来说,可以把数据库连接字符串写到xml,如果要修改数据源,只需要改xml就可以了,没必要再去重 ...
随机推荐
- WebStorm2023安装prettier并生效
1.首先去File > Settings > Plugins 里下载并install插件 Prettier 2.在settings里搜索prettier,按图片所示设置一下Apply 3. ...
- 【python爬虫案例】爬取微博任意搜索关键词的结果,以“唐山打人”为例
目录 一.爬取目标 二.展示爬取结果 三.讲解代码 四.同步视频 4.1 演示视频 4.2 讲解视频 五.附:完整源码 一.爬取目标 大家好,我是马哥. 今天分享一期python爬虫案例,爬取目标是新 ...
- VS Code侧边栏的“资源管理器”丢了
选择"查看"下的"命令面板" 输入:View: Reset View Locations 所有的视图会恢复到默认的位置.
- 零代码零硬件玩转华为云IoT,基于设备联动实时监控设备
本文分享自华为云社区<一键守护,实时洞察:华为云IoT设备联动,智能感知设备状态变化,精准触发告警通知[零代码零硬件玩转华为云IoT]>,作者:周周的奇妙编程. 前言 在前面我们已经体验过 ...
- 基于 ESP8266_RTOS_SDK 实现声控灯
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <stdint.h&g ...
- 详解RocketMQ消息存储原理
本文基于RocketMQ 4.6.0进行源码分析 一. 存储概要设计 RocketMQ存储的文件主要包括CommitLog文件.ConsumeQueue文件.Index文件.RocketMQ将所有to ...
- linux上使用webdav
webdav 干什么用的? 对于我来说,主要是用来同步文件的,n年以前,那时候还啥都不懂,要分享一个文件都是用qq/或者微信发,那时候就一个手机一个电脑,而且文件大部分是分享给认识的人. qq分享完全 ...
- 8.9考试总结(NOIP模拟34)[Merchant·Equation·Rectangle]
一个人有表里两面,你能看到的,仅仅是其中一面而已. 今日已成往昔,明日即将到来,为此理所当然之事,感到无比痛心. T1 Merchant 解题思路 我和正解也许就是差了一个函数(我格局小了..) nt ...
- 自用电脑+外网开放+SSL认证(纯免费)
背景: 本文的目的主要是为了方便大家测试,不过有条件的情况下没必要学习了.主要是给那些没有服务器,公司也不给ssl认证的开发测试人员的一种方案:就像题目所说的那样. 纯免费,纯免费的话是有学习成本的, ...
- CF364E
problem 算法1 我会暴力!!! 直接枚举右上角和左下角,然后计算答案,使用前缀和优化后时间复杂度为 \(O(n^4)\). 算法2 我会分治!!!. 我们知道答案就是左边+右边+两边都有的个数 ...