【進階篇】Java 項目中對使用遞歸的理解分享

前言

筆者在最近的項目開發中,遇到了兩個父子關系緊密相關的場景:評論樹結構、部門樹結構。具體的需求如:找出某條評論下的所有子評論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 地址引用進行篩選。

適用于數據總量不多的情況,如:部門樹,部門數量一般情況是比較固定的,一個組織或者公司最多也就幾百上千個部門。


四、文章小結

筆者確實不推薦在項目中過度使用遞歸,但是合理使用的話也能成為解決特定問題的一個利器,至于怎么拿捏這個度,那就要看大家的具體情況了。

Java 項目中對使用遞歸的理解分享到這里就結束了,文章如有不足和錯誤,或者你有更好的解決思路,歡迎大家的指正和交流!

文章轉載自:CodeBlogMan

原文鏈接:https://www.cnblogs.com/CodeBlogMan/p/18180395

體驗地址:引邁 - JNPF快速開發平臺_低代碼開發平臺_零代碼開發平臺_流程設計器_表單引擎_工作流引擎_軟件架構

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/39439.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/39439.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/39439.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

centos7安裝python3.10

文章目錄 1. 安裝依賴項2. 下載Python 3.10源碼3. 解壓源碼并進入目錄4. 配置安裝選項5. 編譯并安裝Python6. 驗證安裝7.創建軟連接8. 安裝pip39. 換源 1. 安裝依賴項 sudo yum groupinstall -y "Development Tools" sudo yum install -y openssl-devel bzip2-devel…

Eureka的自擴展之道:服務自動擴展的秘訣

&#x1f31f; Eureka的自擴展之道&#xff1a;服務自動擴展的秘訣 在微服務架構中&#xff0c;服務的自動擴展是實現高可用性和彈性伸縮的關鍵。Eureka作為Netflix開源的服務發現框架&#xff0c;提供了一套機制來支持服務的自動擴展。本文將詳細介紹Eureka如何實現服務的自動…

【LeetCode】十、二分查找法:尋找峰值 + 二維矩陣的搜索

文章目錄 1、二分查找法 Binary Search2、leetcode704&#xff1a;二分查找3、leetcode35&#xff1a;搜索插入位置4、leetcode162&#xff1a;尋找峰值5、leetcode74&#xff1a;搜索二維矩陣 1、二分查找法 Binary Search 找一個數&#xff0c;有序的情況下&#xff0c;直接…

第4章:Electron主窗口與子窗口管理

4.1 創建主窗口 主窗口是 Electron 應用啟動后顯示的第一個窗口&#xff0c;通常用來承載應用的主界面。我們使用 BrowserWindow 類來創建主窗口。 4.1.1 創建主窗口的基礎代碼 // 引入 Electron 模塊和 Node.js 的 path 模塊 const { app, BrowserWindow } require(electr…

【動態規劃 前綴和】2478. 完美分割的方案數

本文涉及知識點 劃分型dp 動態規劃匯總 C算法&#xff1a;前綴和、前綴乘積、前綴異或的原理、源碼及測試用例 包括課程視頻 LeetCode 2478. 完美分割的方案數 給你一個字符串 s &#xff0c;每個字符是數字 ‘1’ 到 ‘9’ &#xff0c;再給你兩個整數 k 和 minLength 。 如…

【C++ Primer Plus學習記錄】指針和const

可以用兩種不同的方式將const關鍵字用于指針。第一種方法是讓指針指向一個常量對象&#xff0c;這樣就可以防止使用該指針來修改所指向的值&#xff0c;第二種方法是將指針本身聲明為常量&#xff0c;這樣可以防止改變指針指向的位置。 首先&#xff0c;聲明一個指向常量的指針…

前后端防重復提交(續)

前文介紹過前后端防重復提交的基本場景&#xff0c;簡單的情況是只發起一個異步請求&#xff0c;如果有多個異步請求怎么操作呢&#xff1f;這個要分情況看下。 如果是后端服務器的接口支持一次傳遞多個申請&#xff0c;那么可以將任務放進數組中&#xff0c;發往后端。這是最好…

074、Python 關于實例方法、靜態方法和類方法

在Python中&#xff0c;類可以定義三種類型的方法&#xff1a;實例方法、靜態方法和類方法。每種方法都有其特定的用途和調用方式。 實例方法&#xff08;Instance Methods&#xff09; 定義&#xff1a;實例方法是綁定到類實例上的方法。它們必須有一個名為self的隱式第一個參…

golang 1.22特性之for loop

背景 go1.22版本 for loop每輪循環都生成新的變量. 原諒: https://tip.golang.org/doc/go1.22 Previously, the variables declared by a “for” loop were created once and updated by each iteration. In Go 1.22, each iteration of the loop creates new variables, to …

【C++11】自己封裝RAII類,有哪些坑點?帶你了解移動語義的真相

文章目錄 一、持有資源的類定義移動構造函數的要點1.普通內置類型與std::move2.常見的容器與std::move3.結構體&#xff1a;4.智能指針與std::move 參考 一、持有資源的類定義移動構造函數的要點 1.普通內置類型與std::move 在C中&#xff0c;std::move 主要用于對象的移動語…

Wireshark - tshark支持iptables提供數據包

tshark現在的數據包獲取方式有兩種&#xff0c;分別是讀文件、網口監聽&#xff08;af-packet原始套接字&#xff09;。兩種方式在包獲取上&#xff0c;都是通過讀文件的形式&#xff1b;存在文件io操作&#xff0c;在專門處理大流量的情境下&#xff0c; 我們復用wireshark去做…

Windows編程上

Windows編程[上] 一、Windows API1.控制臺大小設置1.1 GetStdHandle1.2 SetConsoleWindowInfo1.3 SetConsoleScreenBufferSize1.4 SetConsoleTitle1.5 封裝為Innks 2.控制臺字體設置以及光標調整2.1 GetConsoleCursorInfo2.2 SetConsoleCursorPosition2.3 GetCurrentConsoleFon…

python如何輸出list

直接輸出list_a中的元素三種方法&#xff1a; list_a [1,2,3,313,1] 第一種 for i in range(len(list_a)):print(list_a[i]) 1 2 3 313 1 第二種 for i in list_a:print(i) 1 2 3 313 1 第三種&#xff0c;使用enumerate輸出list_a方法&#xff1a; for i&#xff0c;j in enum…

Redis的使用(二)redis的命令總結

1.概述 這一小節&#xff0c;我們主要來研究一下redis的五大類型的基本使用&#xff0c;數據類型如下&#xff1a; redis我們接下來看一看這八種類型的基本使用。我們可以在redis的官網查詢這些命令:Commands | Docs,同時我們也可以用help 數據類型查看命令的幫助文檔。 2. 常…

數據結構 - C/C++ - 串

字符處理 C 特性 C語言中字符串存儲在字符數組中&#xff0c;以空字符\0結束。 字符串常量&#xff0c;const char* str "Hello"&#xff0c;存儲在只讀的數據段中。 布局 字符串在內存中是字符連續存儲的集合&#xff0c;最后一個字符為空字符(ASCII值為0)&…

opencascade AIS_InteractiveContext源碼學習7 debug visualization

AIS_InteractiveContext 前言 交互上下文&#xff08;Interactive Context&#xff09;允許您在一個或多個視圖器中管理交互對象的圖形行為和選擇。類方法使這一操作非常透明。需要記住的是&#xff0c;對于已經被交互上下文識別的交互對象&#xff0c;必須使用上下文方法進行…

【問題已解決】Vue管理后臺,點擊登錄按鈕,會發起兩次網絡請求(竟然是vscode Compile Hero編譯插件導致的)

問題 VueElement UI 做的管理后臺&#xff0c;點擊登錄按鈕&#xff0c;發現 接口會連續掉兩次&#xff0c;發起兩次網絡請求&#xff0c;但其他接口都是正常調用的&#xff0c;沒有這個問題&#xff0c;并且登錄按鈕也加了loading&#xff0c;防止重復點擊&#xff0c;于是開…

搜索引擎常用語法

引號 (" "): 用雙引號將詞組括起來&#xff0c;搜索引擎將返回包含完全相同短語的結果。 示例&#xff1a;"人工智能發展趨勢" 減號 (-): 在關鍵詞前加上減號可以排除包含特定詞語的結果。 示例&#xff1a;人工智能 -機器學習&#xff08;排除包含 “機器…

樸素貝葉斯解密:sklearn中的分類器工作原理

&#x1f4da; 樸素貝葉斯解密&#xff1a;sklearn中的分類器工作原理 在機器學習領域&#xff0c;樸素貝葉斯分類器因其簡單、高效而廣受歡迎。特別是在處理大量特征數據時&#xff0c;樸素貝葉斯表現出了卓越的性能。scikit-learn&#xff08;簡稱sklearn&#xff09;是Pyth…

JavaMySQL 學習(基礎)

目錄 Java CMD Java發展 計算機存儲規則 Java學習 switch新用法&#xff08;可以當做if來使用&#xff09; 數組定義 隨機數 Java內存分配 MySQL MySQL概述 啟動和停止 客戶端連接 數據模型 關系型數據庫 SQL SQL通用語法 SQL分類 DDL--數據定義語言 數據庫…