【进阶篇】Java 项目中对使用递归的理解分享

news/2024/7/7 20:09:54/文章来源:https://www.cnblogs.com/CodeBlogMan/p/18180395

【进阶篇】Java 项目中对使用递归的理解分享

目录
  • 【进阶篇】Java 项目中对使用递归的理解分享
    • 前言
    • 一、什么是递归
      • 1.1基本概念
      • 1.2优缺点
      • 1.3与迭代的区别
    • 二、实际案例
    • 三、改进方案
      • 3.1控制递归层数
      • 3.1用 Stream 遍历
    • 四、文章小结

前言

笔者在最近的项目开发中,遇到了两个父子关系紧密相关的场景:评论树结构、部门树结构。具体的需求如:找出某条评论下的所有子评论id集合,找出某个部门下所有的子部门id集合。

在之前的项目开发经验中,递归使用得是较少的,但作为一个在数据结构操作中遍历树节点的解决方案,我还是拿出来作为技术积累进行记录以及分享。

一、什么是递归

1.1基本概念

这里就有必要简单介绍一下关于递归的基本概念了。

在 Java 中,递归是指在方法的定义中调用自身的过程,递归是基于方法调用栈的原理实现的:当一个方法被调用时,会在调用栈中创建一个对应的栈帧,包含方法的参数、局部变量和返回地址等信息。在递归中,方法会在自身的定义中调用自身,这会导致多个相同方法的栈帧依次入栈。当满足终止条件时,递归开始回溯,栈帧依次出栈,方法得以执行完毕。

递归的关键是定义好递归的终止条件和递归调用的条件。如果没有适当的终止条件或递归调用的条件不满足,递归可能会陷入无限循环,导致栈内存溢出。

1.2优缺点

优点:

  1. 简化问题:递归能够将复杂问题分解成更小规模的子问题,简化了问题的解决过程;
  2. 实现高效算法:递归在某些算法中能够实现高效的解决方法,如数据结构操作中遍历树节点等。

缺点:

  1. 栈溢出风险:递归可能导致方法调用栈过深,造成栈内存溢出;
  2. 性能损耗:递归调用需要创建多个栈帧,对系统资源有一定的消耗;
  3. 可读性不高:递归的使用需要谨慎,不合理地使用可能造成代码难以理解和调试。

1.3与迭代的区别

  • 迭代(Iteration)

    迭代常见于 for 循环中:比如有一个集合 A,对 A 进行 foreach,在内部设置条件,符合条件后将集合中某个元素的值替换成别的值。

迭代示例简图
    @Testpublic 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 项目中对使用递归的理解分享到这里就结束了,文章如有不足和错误,或者你有更好的解决思路,欢迎大家的指正和交流!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/736133.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#/.NET/.NET Core编程技巧练习集(学习,实践干货)

DotNet Exercises介绍 DotNetGuide专栏C#/.NET/.NET Core编程常用语法、算法、技巧、中间件、类库练习集,配套详细的文章教程讲解,助你快速掌握C#/.NET/.NET Core各种编程常用语法、算法、技巧、中间件、类库等等。GitHub开源地址:https://github.com/YSGStudyHards/DotNetE…

2024年6月文章一览

2024年6月编程人总共更新了5篇文章: 1.2024年5月文章一览 2.《编译原理》阅读笔记:p18 3.《编译原理》阅读笔记:p19-p24 4.《编译原理》阅读笔记:p25-p32 5.《Programming from the Ground Up》阅读笔记:p1-p18 6月再挖一个坑,开始《Programming from the Ground Up》的学…

【论文阅读】自动驾驶光流任务 DeFlow: Decoder of Scene Flow Network in Autonomous Driving

再一次轮到讲自己的paper!耶,宣传一下自己的工作,顺便完成中文博客的解读 方便大家讨论。Title PictureReference and pictures paper: https://arxiv.org/abs/2401.16122 code: https://github.com/KTH-RPL/DeFlow b站视频: https://www.bilibili.com/video/BV1GH4y1w7LQ1.…

利用SonarCloud和Azure DevOps提升代码质量

利用SonarCloud和Azure DevOps提升代码质量 在软件开发过程中,代码质量是至关重要的。为了确保代码的清洁和安全性,我们可以使用SonarCloud——一个基于云的代码质量与安全服务。SonarCloud不仅对开源项目免费,还为私有项目提供了14天的免费试用。本文将指导您如何将SonarCl…

免费申请一张SSL证书-包含100个域名-挑战!!

挑战一下,申请一张包含100个域名的证书 首先,我们访问来此加密网站,进入登录页面,输入我的账号密码。登录后,咱们就可以开始申请证书,首先说一下,咱账号是SVIP哦,只有SVIP才可以申请包含100个域名的证书。为了方便验证,咱么先添加一下域名接口,这样可以自动配置验证信…

.net6+ 在单文件应用程序中获取程序集位置

一般来说,获取执行程序集的位置,您可以调用: var executableDirectory = System.Reflection.Assembly.GetExecutingAssembly().Location;如果发布为单个文件, 会提示如下警告 warning IL3000: System.Reflection.Assembly.Location always returns an empty string for assemb…

(set+拓扑排序) CF1572A Book

题意:思路: 每本书之间很明显存在拓扑关系,由此想到拓扑排序。使用set对图进行拓扑排序,将阅读次数小的放在前面,若阅读次数相同则按照阅读章节编号排序。假设第 x 章在第 y 章理解之后就能理解,若 x 大于 y 则本次阅读就可以理解 x 章,否则需要下一次才能理解第 x 章。 …

【github报错解决】Failed to connect to github.com port 443: Couldnt connect to server

今天使用github push时候报错的 解决办法: 1、查看本机代理 路径:设置->网络和Internet->代理->地址:端口 2、用git cmd修改配置,port就用刚刚代理端口值git config --global http.proxy http://127.0.0.1:[port] git config --global https.proxy http://127.0.0…

LVGL组件

LVGL组件的使用 目录LVGL组件的使用1 . 父和子对象2. 部件的基本属性及设置3. 图解:还有其他样式可以设置2. 组件分类 1 . 父和子对象2. 部件的基本属性及设置 /******** 设置打小 ********/ /******** 注意:设置部件位置时,坐标原点在父对象的左上角 ********/ lv_…

Maven 笔记

开发工具:Maven相关内容笔记# Maven简介 Maven的本质是一个项目管理工具,将项目开发和管理过程抽象成一个项目对象模型(POM)) 这玩意儿是使用Java开发的,所以采用的就是Java的思想:面向对象 POM (Project Object Model):项目对象模型Maven的作用:项目构建:提供标准的、…

202406月度小结

这个学期比上个学期有意思得多了。但是为什么我只写了6月小结,没有写3月4月5月小结呢?因为那时候还没有想到用博客写这种小结形式的随笔啦…… 横向对比,这四个月都挺有意思的。但是由于我太容易忘记事情了……我的记忆是有限的,先把6月发生的趣事赶紧记录下来比较好。 昨天…

大模型技术方向Task1笔记

赛题概要 一、赛事背景 在当今数字化时代,企业积累了丰富的对话数据,这些数据不仅是客户与企业之间交流的记录,更是隐藏着宝贵信息的宝库。在这个背景下,群聊对话分角色要素提取成为了企业营销和服务的一项重要策略。 群聊对话分角色要素提取的理念是基于企业对话数据的深度…

[LeetCode] 122. Best Time to Buy and Sell Stock II

medium是你的谎言. class Solution:def maxProfit(self, prices: List[int]) -> int:#1if len(prices) == 1:return 0#elsemax_profit = 0min_price = prices[0]for i, element in enumerate(prices):#find a min_price if element <= min_price:min_price = elementelse…

【esp32 学习笔记】esp-idf学会调用组件管理——以button

简单不看版——esp-idf组件管理步骤 在ESP-IDF 组件管理器网页(https://components.espressif.com/)搜索我们需要的组件,比如【button】,然后 点开相应的组件,比如 espressif/button 组件。 【关键步骤】复制相关组件界面上配置组件的命令 ,形如:idf.py add-dependency &…

idea创建javaweb项目

1.新建project项目 2.添加依赖<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>6.0.10</version> </dependency>…

Linguistics-English-Would, Should, and Could: How to Use Them Correctly

https://7esl.com/would-should-could/ Key Takeaways“Would” is used for hypotheticals and future possibilities that may not occur. “Should” implies advice, expectation, or probability. “Could” expresses past ability or present possibility.Home Knowled…

[LeetCode] 121. Best Time to Buy and Sell Stock

想清楚了确实算是简单题. class Solution:def maxProfit(self, prices: List[int]) -> int:#1if len(prices) == 1:return 0#elsemax_profit = 0min_price = prices[0]for i, element in enumerate(prices):if element <= min_price:min_price = elementelif element - m…

Druid数据库加密实现

Druid数据库连接池中的密码加密功能提供了以下几个主要好处: 增强安全性:最显著的好处是提高了数据库系统的安全性。明文存储数据库密码容易造成安全隐患,一旦配置文件泄露,数据库可能遭受未授权访问。通过加密数据库密码,即使配置文件落入未经授权的人员手中,他们也无法…

17-移动端适配

移动端适配01 单位的分类02 相对单位相对的对象 2.1 em 永远都是相对于自身的font-size <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, …

maven下载安装配置

Maven 是一款为 Java 项目管理构建、依赖管理的工具(软件),使用 Maven 可以自动化构建、测试、打包和发布项目,大大提高了开发效率和质量。 Maven的两大核心作用: 1.依赖管理: Maven 可以管理项目的依赖,包括自动下载所需依赖库、自动下载依赖需要的依赖并且保证版本没有…