【Java SE 基础学习打卡】32 方法的嵌套调用与递归调用

目录

  • 前言
  • [一、方法的嵌套调用:让方法 "协作完成任务"](#一、方法的嵌套调用:让方法 “协作完成任务”)
    • [1.1 生活化类比(秒懂)](#1.1 生活化类比(秒懂))
    • [1.2 编程定义](#1.2 编程定义)
    • [1.3 执行流程详解](#1.3 执行流程详解)
    • [1.4 代码示例(计算三科平均分,嵌套调用求和方法)](#1.4 代码示例(计算三科平均分,嵌套调用求和方法))
    • [1.5 执行结果与流程拆解](#1.5 执行结果与流程拆解)
    • [1.6 关键提醒:嵌套调用的执行顺序](#1.6 关键提醒:嵌套调用的执行顺序)
  • [二、方法的递归调用:让方法 "自己调用自己"](#二、方法的递归调用:让方法 “自己调用自己”)
    • [2.1 生活化类比(秒懂递归核心)](#2.1 生活化类比(秒懂递归核心))
    • [2.2 编程定义](#2.2 编程定义)
    • [2.3 递归的核心条件(必须同时满足,否则栈溢出)](#2.3 递归的核心条件(必须同时满足,否则栈溢出))
    • [2.4 关键警告:缺少终止条件的后果](#2.4 关键警告:缺少终止条件的后果)
  • 三、递归的实际应用示例(新手能看懂的简单案例)
    • [3.1 示例 1:求 n 的阶乘(最经典的递归案例)](#3.1 示例 1:求 n 的阶乘(最经典的递归案例))
    • [3.2 示例 2:求斐波那契数列的第 n 项](#3.2 示例 2:求斐波那契数列的第 n 项)
    • [3.3 示例 3:递归遍历文件夹](#3.3 示例 3:递归遍历文件夹)
  • 四、递归的优缺点与使用场景
    • [4.1 递归的优点](#4.1 递归的优点)
    • [4.2 递归的缺点](#4.2 递归的缺点)
    • [4.3 递归的使用场景](#4.3 递归的使用场景)
  • [五、新手必避的 4 个 "致命坑"](#五、新手必避的 4 个 “致命坑”)
    • [5.1 坑 1:忘记写递归终止条件(栈溢出)](#5.1 坑 1:忘记写递归终止条件(栈溢出))
    • [5.2 坑 2:递归表达式写错(逻辑错误)](#5.2 坑 2:递归表达式写错(逻辑错误))
    • [5.3 递归层级过深(栈溢出)](#5.3 递归层级过深(栈溢出))
    • [5.4 混淆 "方法嵌套定义" 和 "嵌套调用"](#5.4 混淆 “方法嵌套定义” 和 “嵌套调用”)
  • 总结

前言

上一节咱们吃透了方法的返回值和 void 关键字,现在写代码时会遇到更灵活的调用场景:比如计算三科平均分,需要先调用 "求和方法" 算出总分,再在 "平均分方法" 里用这个总分 ------ 这是方法的嵌套调用;又比如计算 5 的阶乘(5! = 5×4×3×2×1),用循环能做,但用递归会更简洁 ------ 这是方法的递归调用。

很多新手觉得递归 "绕",核心是没抓住 "终止条件 + 递推逻辑";嵌套调用则是新手从 "单方法编程" 到 "多方法协作" 的关键。这一节咱们用生活化例子 + 分步拆解,先讲嵌套调用的执行流程,再啃递归的核心规则,最后通过实际例子落地,让新手既能看懂又能写出来!

一、方法的嵌套调用:让方法 "协作完成任务"

1.1 生活化类比(秒懂)

做一顿番茄炒蛋的流程:

  • 核心方法:makeTomatoEgg()(做番茄炒蛋);

  • 嵌套调用:

    1. washTomato()(洗番茄)------ 在切番茄前调用;

    2. cutTomato()(切番茄)------ 在炒之前调用;

    3. beatEgg()(打鸡蛋)------ 和切番茄并行调用;

  • 核心:一个主方法里调用多个子方法,子方法还能调用更小的方法,像流水线一样协作。

1.2 编程定义

在一个方法的方法体中,调用另一个方法,这种调用方式就叫方法的嵌套调用。Java 中方法不能嵌套定义,但可以嵌套调用(新手别搞混!)。

1.3 执行流程详解

先看核心流程图,再看代码示例,新手能直观理解执行顺序:

1.4 代码示例(计算三科平均分,嵌套调用求和方法)

java 复制代码
public class NestedCallDemo {
    // 子方法1:计算三个数的和(被嵌套调用)
    public static int add(int a, int b, int c) {
        System.out.println("进入add方法,开始求和");
        int sum = a + b + c;
        System.out.println("add方法求和完成,返回:" + sum);
        return sum;
    }

    // 子方法2:计算平均分(嵌套调用add方法)
    public static double calcAvg(int chinese, int math, int english) {
        System.out.println("进入calcAvg方法,准备调用add方法");
        // 嵌套调用add方法,获取总分
        int total = add(chinese, math, english);
        double avg = total / 3.0;
        System.out.println("calcAvg方法计算完成,平均分:" + avg);
        return avg;
    }

    public static void main(String[] args) {
        System.out.println("main方法开始执行");
        // 调用calcAvg方法,触发嵌套调用
        double result = calcAvg(90, 85, 95);
        System.out.println("main方法执行完成,最终平均分:" + result);
    }
}

1.5 执行结果与流程拆解

执行结果:

java 复制代码
main方法开始执行
进入calcAvg方法,准备调用add方法
进入add方法,开始求和
add方法求和完成,返回:270
calcAvg方法计算完成,平均分:90.0
main方法执行完成,最终平均分:90.0

流程拆解(新手逐句懂):

  1. main 方法先执行,调用calcAvg(90,85,95)

  2. 跳转到 calcAvg 方法,执行到add(90,85,95)时,暂停 calcAvg,跳转到 add 方法;

  3. add 方法执行完求和,返回 270 给 calcAvg 的 total 变量;

  4. calcAvg 恢复执行,用 total 计算平均分,返回 90.0 给 main 的 result 变量;

  5. main 恢复执行,打印最终结果,程序结束。

1.6 关键提醒:嵌套调用的执行顺序

  • 调用方法时,当前方法暂停,优先执行被调用的方法;

  • 被调用方法执行完返回结果后,当前方法继续执行剩余代码;

  • 嵌套层数没有严格限制,但层数过多会增加代码理解难度,新手建议控制在 3 层以内。

二、方法的递归调用:让方法 "自己调用自己"

2.1 生活化类比(秒懂递归核心)

数台阶的例子:

  • 你站在第 5 级台阶,想知道总共有多少级台阶;

  • 你问第 4 级台阶的人:"你那边有多少级?"(调用自身);

  • 第 4 级问第 3 级,第 3 级问第 2 级,第 2 级问第 1 级;

  • 第 1 级的人说:"我这是最后 1 级,就 1 级"(终止条件);

  • 然后从第 1 级往回算:2 级 = 1+1,3 级 = 2+1,4 级 = 3+1,5 级 = 4+1(递归表达式);

  • 核心:自己问自己,直到问到 "终点",再往回算。

2.2 编程定义

在一个方法的方法体中,直接或间接调用该方法本身,这种调用方式就叫递归调用。

  • 直接递归:方法 A 里调用方法 A(比如阶乘方法里调用自己);

  • 间接递归:方法 A 调用方法 B,方法 B 又调用方法 A(新手少用,易出错)。

2.3 递归的核心条件(必须同时满足,否则栈溢出)

递归不是 "无限循环",必须满足两个核心条件,缺一不可:

条件 1:终止条件(递归的 "终点")

告诉方法什么时候停止调用自己,比如数台阶时 "第 1 级台阶" 就是终止条件。

条件 2:递归表达式(递推逻辑)

把大问题拆成更小的同类问题,比如5! = 5 × 4!4! = 4 × 3!,直到拆到终止条件(1! = 1)。

2.4 关键警告:缺少终止条件的后果

如果没有终止条件,方法会无限调用自己,JVM 的方法栈会被占满,抛出StackOverflowError(栈溢出错误),程序直接崩溃。

三、递归的实际应用示例(新手能看懂的简单案例)

3.1 示例 1:求 n 的阶乘(最经典的递归案例)

阶乘定义:n! = n × (n-1) × (n-2) × ... × 1,终止条件:1! = 10! = 1

java 复制代码
public class FactorialDemo {
    // 递归方法:求n的阶乘
    public static int factorial(int n) {
        // 终止条件:n=0或n=1时,返回1
        if (n == 0 || n == 1) {
            return 1;
        }
        // 递归表达式:n! = n × (n-1)!
        return n * factorial(n - 1);
    }

    public static void main(String[] args) {
        int n = 5;
        int result = factorial(5);
        System.out.println(n + "的阶乘:" + result); // 输出120
    }
}

执行流程拆解(5! 为例):

java 复制代码
factorial(5) → 5 × factorial(4)
factorial(4) → 4 × factorial(3)
factorial(3) → 3 × factorial(2)
factorial(2) → 2 × factorial(1)
factorial(1) → 终止条件,返回1
往回计算:2×1=2 → 3×2=6 → 4×6=24 →5×24=120

3.2 示例 2:求斐波那契数列的第 n 项

斐波那契数列定义:F(0)=0F(1)=1F(n)=F(n-1)+F(n-2)(n≥2)。

java 复制代码
public class FibonacciDemo {
    // 递归方法:求斐波那契数列第n项
    public static int fibonacci(int n) {
        // 终止条件
        if (n == 0) {
            return 0;
        } else if (n == 1) {
            return 1;
        }
        // 递归表达式
        return fibonacci(n - 1) + fibonacci(n - 2);
    }

    public static void main(String[] args) {
        int n = 6;
        int result = fibonacci(n);
        System.out.println("斐波那契数列第" + n + "项:" + result); // 输出8
    }
}

3.3 示例 3:递归遍历文件夹

递归最常用的场景是 "树形结构遍历",比如文件夹里有子文件夹,子文件夹里还有文件,用递归能逐层遍历:

java 复制代码
import java.io.File;

public class FileTraverseDemo {
    // 递归方法:遍历文件夹
    public static void traverseFolder(File folder) {
        // 终止条件:如果不是文件夹,直接打印路径
        if (!folder.isDirectory()) {
            System.out.println("文件:" + folder.getAbsolutePath());
            return;
        }

        // 递归表达式:遍历当前文件夹下的所有文件/子文件夹
        File[] files = folder.listFiles();
        if (files == null) { // 避免空指针(文件夹无权限访问)
            return;
        }
        for (File f : files) {
            System.out.println("遍历:" + f.getAbsolutePath());
            traverseFolder(f); // 递归调用,遍历子文件夹
        }
    }

    public static void main(String[] args) {
        // 替换成你电脑上的文件夹路径(比如D:/test)
        File folder = new File("D:/test");
        traverseFolder(folder);
    }
}

执行逻辑:

  • 传入一个文件夹,先判断是不是文件夹(终止条件:不是则打印文件路径);

  • 如果是文件夹,遍历里面的所有文件 / 子文件夹;

  • 对每个子文件夹,再次调用traverseFolder方法,直到所有层级遍历完成。

四、递归的优缺点与使用场景

4.1 递归的优点

  • 代码简洁:用少量代码实现复杂的递推逻辑,比如阶乘、斐波那契数列,递归比循环代码更短;

  • 逻辑直观:符合人类 "拆大问题为小问题" 的思维习惯,比如遍历文件夹,递归思路更自然;

  • 适合树形结构:文件夹、二叉树等树形结构的遍历,递归是最优选择(循环实现复杂)。

4.2 递归的缺点

  • 栈溢出风险:递归层级过深(比如求 10000 的阶乘),会耗尽方法栈内存,抛出StackOverflowError

  • 效率较低:递归需要频繁创建方法栈帧,且可能重复计算(比如斐波那契数列会重复计算 F (3)、F (2) 等);

  • 调试困难:递归调用链长,新手调试时很难跟踪每一步的执行过程。

4.3 递归的使用场景

适合用递归的场景:

  • 数学递推问题:阶乘、斐波那契数列、汉诺塔等;

  • 树形结构遍历:文件夹遍历、二叉树的增删改查;

  • 分治算法:快速排序、归并排序(后续会学)。

不适合用递归的场景:

  • 性能要求高的场景:比如高频调用的接口、大数据量计算;

  • 递归层级过深的场景:比如求 10000 的阶乘(用循环更安全);

  • 简单循环能实现的场景:比如计算 1-100 的和(循环更高效)。

五、新手必避的 4 个 "致命坑"

5.1 坑 1:忘记写递归终止条件(栈溢出)

  • 错误示例:

    java 复制代码
    public static int factorial(int n) {
        // 无终止条件,无限递归
        return n * factorial(n - 1);
    }
  • 后果:运行时抛出StackOverflowError,程序崩溃;

  • 避坑:写递归时先写终止条件,再写递归表达式。

5.2 坑 2:递归表达式写错(逻辑错误)

  • 错误示例(阶乘):

    java 复制代码
    public static int factorial(int n) {
        if (n == 1) return 1;
        return n * factorial(n + 1); // 应该是n-1,写成n+1
    }
  • 后果:递归层级无限增加,栈溢出;

  • 避坑:先理清递推逻辑,比如阶乘是 "n×(n-1)!",不是 "n×(n+1)!"。

5.3 递归层级过深(栈溢出)

  • 错误示例:

    java 复制代码
    // 求10000的阶乘,递归层级太深
    int result = factorial(10000);
  • 后果:栈溢出;

  • 避坑:层级超过 1000 的场景,改用循环实现,或用尾递归(新手暂不用掌握)。

5.4 混淆 "方法嵌套定义" 和 "嵌套调用"

  • 错误示例:

    java 复制代码
    // 方法里嵌套定义方法,编译报错
    public static int add(int a, int b) {
        public static int sub(int x, int y) {
            return x - y;
        }
        return a + b;
    }
  • 后果:编译报错 "方法内不能定义方法";

  • 避坑:Java 不支持方法嵌套定义,但支持嵌套调用,方法必须平级定义。

总结

这一节咱们掌握了方法嵌套调用和递归调用的核心知识,记住 3 个核心点:

  1. 嵌套调用:方法间协作,调用其他方法时当前方法暂停,执行完返回后继续,执行顺序是 "先深入,再返回";

  2. 递归调用:方法自身调用自身,必须有终止条件 + 递归表达式,避免栈溢出;

  3. 递归取舍:适合递推、树形结构场景,避免层级过深或性能敏感场景。

嵌套调用是编程模块化的基础,递归是解决复杂递推问题的利器,掌握这两种调用方式,你的 Java 方法使用会更灵活。

相关推荐
编程火箭车20 小时前
【Java SE 基础学习打卡】31 方法的返回值与void关键字
java se·java 基础·return 语句·编程小白入门·java 方法·方法返回值·void 关键字
编程火箭车3 天前
【Java SE 基础学习打卡】28 方法的定义与调用
java se·参数传递·返回值·java 基础·新手避坑·java 方法·方法定义与调用
编程火箭车16 天前
【Java SE 基础学习打卡】27 方法的概述
编程语法·java se·代码复用·编程小白入门·方法概述·功能模块化·java 方法
编程火箭车1 个月前
【Java SE 基础学习打卡】24 循环结构 - while
java·编程基础·循环结构·while循环·java se·do-while循环·避免死循环
编程火箭车1 个月前
【Java SE 基础学习打卡】23 分支语句 - switch
编程语法·java se·switch 语句·新手避坑·java 分支结构·编程小白入门·等值判断
BillKu4 个月前
Java核心概念详解:JVM、JRE、JDK、Java SE、Java EE (Jakarta EE)
java·jvm·jdk·java ee·jre·java se·jakarta ee
叶 落7 个月前
[Java 基础]正则表达式
java·正则表达式·java 基础
叶 落7 个月前
[Java 基础]数组
java·java 基础
叶 落7 个月前
[Java 基础]注释
java·开发语言·java 基础