【Java报错】记录一次调用递归方法导致的 StackOverFlowError 及如何重构递归代码避免栈溢出

简介: 【Java报错】记录一次调用递归方法导致的 StackOverFlowError 及如何重构递归代码避免栈溢出

1. 递归方法

以下代码为核心代码,省略了不必要的业务流程,用来说明问题:

private boolean getRecommendListAndMatch(String newCycleEndDateStr, String tag) {
        boolean isMatchSuccess = false;
        // 获取数据
        BaseResult recommendList = dataMatchService.getRecommendListByPara();
        // 数据处理
        assert recommendList != null;
        if (recommendList.data.total > 0) {
          // 比较复杂的业务流程
            isMatchSuccess = true;
        } else {
            log.info("结束时间:{} 内无符合条件的数据!", newCycleEndDateStr);
        }
        // 递归调用方法
        if (isMatchSuccess) {
            getRecommendListAndMatch(newCycleEndDateStr, tag);
        }
        return isMatchSuccess;
    }

在网上找了几张图片:

这是一个很典型的递归调用

栈是一种比较简单的数据结构,具有后进先出的特性。

栈本身是一个线性表,但是在这个表中只有一端允许数据的进出。那为什么方法要放在栈结构内呢?比如方法A调用方法B,方法A的栈帧要先入栈(A的方法体和参数都要保存到B方法调用结束,否则B方法用的A里的参数就会出现空指针了)然后入栈B方法的数据,B方法调用结束,B方法的栈帧被弹出,此时如果A方法不再调用其他方法,A方法执行完了之后也会被弹出。

可想而知,如果递归方法的参数、对象较多再加上递归层数较多时,栈的空间毕竟是有限的,溢出就不足为奇了。

2. 重构代码

重构递归方法的逻辑不复杂,这里提供一种,使用 do while 实现递归逻辑:

// 1. 调用条件
  boolean isMatchSuccess;
  do {
    // 2. 修改调用条件
    isMatchSuccess = getRecommendListAndMatch(newCycleEndDateStr, tag);
  // 3. 判断调用条件是否成立
  } while (isMatchSuccess);

三个点:

  • 判断是否再次调用的条件
  • do方法体里对调用条件的处理
  • while再次判断调用条件

主方法的调整(删掉递归调用):

private boolean getRecommendListAndMatch(String newCycleEndDateStr, String tag) {
        boolean isMatchSuccess = false;
        // 获取数据
        BaseResult recommendList = dataMatchService.getRecommendListByPara();
        // 数据处理
        assert recommendList != null;
        if (recommendList.data.total > 0) {
          // 比较复杂的业务流程
            isMatchSuccess = true;
        } else {
            log.info("结束时间:{} 内无符合条件的数据!", newCycleEndDateStr);
        }
        return isMatchSuccess;
    }

重构后,每次调用结束,方法帧都会从栈内弹出,从而避免了长时间过度占用栈空间的问题。

目录
相关文章
|
4天前
|
存储 Java 索引
Java快速入门之数组、方法
### Java快速入门之数组与方法简介 #### 一、数组 数组是一种容器,用于存储同种数据类型的多个值。定义数组时需指定数据类型,如`int[]`只能存储整数。数组的初始化分为静态和动态两种: - **静态初始化**:直接指定元素,系统自动计算长度,如`int[] arr = {1, 2, 3};` - **动态初始化**:手动指定长度,系统给定默认值,如`int[] arr = new int[3];` 数组访问通过索引完成,索引从0开始,最大索引为`数组.length - 1`。遍历数组常用`for`循环。常见操作包括求和、找最值、统计特定条件元素等。
|
1天前
|
安全 Java 程序员
Java面试必问!run() 和 start() 方法到底有啥区别?
在多线程编程中,run和 start方法常常让开发者感到困惑。为什么调用 start 才能启动线程,而直接调用 run只是普通方法调用?这篇文章将通过一个简单的例子,详细解析这两者的区别,帮助你在面试中脱颖而出,理解多线程背后的机制和原理。
29 12
|
2天前
|
算法 Java API
Java 方法注释:规范、实用和高质量的写法
本文深入探讨了如何编写高质量的 Java 方法注释
25 11
|
24天前
|
Java Maven
java项目中jar启动执行日志报错:no main manifest attribute, in /www/wwwroot/snow-server/z-server.jar-jar打包的大小明显小于正常大小如何解决
在Java项目中,启动jar包时遇到“no main manifest attribute”错误,且打包大小明显偏小。常见原因包括:1) Maven配置中跳过主程序打包;2) 缺少Manifest文件或Main-Class属性。解决方案如下:
java项目中jar启动执行日志报错:no main manifest attribute, in /www/wwwroot/snow-server/z-server.jar-jar打包的大小明显小于正常大小如何解决
|
2天前
|
SQL Java 数据库连接
【潜意识Java】Java中JDBC过时方法的替代方案以及JDBC为什么过时详细分析
本文介绍了JDBC中一些常见过时方法及其替代方案。
21 5
|
8天前
|
SQL Java 数据库连接
如何在 Java 代码中使用 JSqlParser 解析复杂的 SQL 语句?
大家好,我是 V 哥。JSqlParser 是一个用于解析 SQL 语句的 Java 库,可将 SQL 解析为 Java 对象树,支持多种 SQL 类型(如 `SELECT`、`INSERT` 等)。它适用于 SQL 分析、修改、生成和验证等场景。通过 Maven 或 Gradle 安装后,可以方便地在 Java 代码中使用。
103 11
|
13天前
|
Java Windows
【Azure Function】部署Java Function失败:报错deploy [ERROR] Status code 401和警告 'China North 3' may not be a valid region
1:deploy [ERROR] Status code 401, (empty body). 2: China North 3 may not be a valid region,please refer to https://aka.ms/maven_function_configuration#supported-regions for values. 3:  <azure.functions.maven.plugin.version>1.36.0</azure.functions.maven.plugin.version>
27 11
|
12天前
|
JSON Java 数据挖掘
利用 Java 代码获取淘宝关键字 API 接口
在数字化商业时代,精准把握市场动态与消费者需求是企业成功的关键。淘宝作为中国最大的电商平台之一,其海量数据中蕴含丰富的商业洞察。本文介绍如何通过Java代码高效、合规地获取淘宝关键字API接口数据,帮助商家优化产品布局、制定营销策略。主要内容包括: 1. **淘宝关键字API的价值**:洞察用户需求、优化产品标题与详情、制定营销策略。 2. **获取API接口的步骤**:注册账号、申请权限、搭建Java开发环境、编写调用代码、解析响应数据。 3. **注意事项**:遵守法律法规与平台规则,处理API调用限制。 通过这些步骤,商家可以在激烈的市场竞争中脱颖而出。
|
29天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
57 3