目录
- 前言
- [一、方法的嵌套调用:让方法 "协作完成任务"](#一、方法的嵌套调用:让方法 “协作完成任务”)
-
- [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()(做番茄炒蛋); -
嵌套调用:
-
washTomato()(洗番茄)------ 在切番茄前调用; -
cutTomato()(切番茄)------ 在炒之前调用; -
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
流程拆解(新手逐句懂):
-
main 方法先执行,调用
calcAvg(90,85,95); -
跳转到 calcAvg 方法,执行到
add(90,85,95)时,暂停 calcAvg,跳转到 add 方法; -
add 方法执行完求和,返回 270 给 calcAvg 的 total 变量;
-
calcAvg 恢复执行,用 total 计算平均分,返回 90.0 给 main 的 result 变量;
-
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! = 1,0! = 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)=0,F(1)=1,F(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:忘记写递归终止条件(栈溢出)
-
错误示例:
javapublic static int factorial(int n) { // 无终止条件,无限递归 return n * factorial(n - 1); } -
后果:运行时抛出
StackOverflowError,程序崩溃; -
避坑:写递归时先写终止条件,再写递归表达式。
5.2 坑 2:递归表达式写错(逻辑错误)
-
错误示例(阶乘):
javapublic 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 个核心点:
-
嵌套调用:方法间协作,调用其他方法时当前方法暂停,执行完返回后继续,执行顺序是 "先深入,再返回";
-
递归调用:方法自身调用自身,必须有终止条件 + 递归表达式,避免栈溢出;
-
递归取舍:适合递推、树形结构场景,避免层级过深或性能敏感场景。
嵌套调用是编程模块化的基础,递归是解决复杂递推问题的利器,掌握这两种调用方式,你的 Java 方法使用会更灵活。