正在加载...

Java 中的递归

写一个名为 down 的方法,接收一个名为 n 的 int。若 n 为 0 或更小,用空的 return; 停止。否则打印 n,再从同一方法内调用 down(n - 1)。从 main 调用 down(3);。屏幕应显示三行:3,然后 2,然后 1。方法用更小的数不断调用自己,直到停止条件结束整条链。带停止条件的自调用,就是本页要教的内容。

调用自己的方法

在作用域那一页,mark 跑一次就结束。这里 down 先运行,再在第一次调用完全结束前,用新参数启动另一个 down。每次调用都有自己的 n。只有某次调用撞上停止条件,链才会结束。
新建一个小程序文件,名叫 Ladder.java。第一行的 public class 必须与文件名一致:public class Ladder。不要在这个文件里留下旧的 Gate 或 Sign 类名;编译器要求类名与文件名一致。
在 main 外面、仍在类的花括号里,写 static void down(int n)。本页保持 void:每一步负责打印,不需要通过带类型的 return 带回值。停止条件用无返回值的 return;——只结束这次调用。编译前先保存文件。完整的第一个程序在下一个代码框里。

带 down 的 Ladder.java

这是你先编译的版本。main 里放一次调用。down 里放停止、打印和自调用。

down prints then calls itself

java
public class Ladder {
    public static void main(String[] args) {
        down(3);
    }

    static void down(int n) {
        if (n <= 0) {
            return;
        }
        System.out.println(n);
        down(n - 1);
    }
}

在存放 Ladder.java 的文件夹里运行 javac Ladder.java。当圆括号、花括号和分号都匹配时,javac 自己不会写出一行。然后运行 java Ladder——不要写成 java Ladder.java。第二个命令去掉 .java 后缀。这两条命令在 Windows 和 macOS 上都一样。程序应打印三行后停止:3,然后 2,然后 1。它不应等待你输入。如果终端在等键盘输入,说明文件里还留着旧练习的 nextLine 调用。删掉它,只保留上面的 down(3); 和 down 方法。
按顺序把这次运行走一遍。Java 从 main 开始。调用 down(3) 进入时 n 等于 3。它大于 0,所以方法打印 3,再调用 down(2)。第二次调用打印 2,再调用 down(1)。第三次打印 1,再调用 down(0)。带 0 的那次调用撞上 n <= 0,不打印就返回。链结束。屏幕保留三行已打印内容。
这些调用中的每一次,都是同一段方法文本的单独一遍。第一遍在启动 down(2) 之后还有事要做——等内部调用返回后再结束。你不是写四个不同的方法名。你写一个方法,让更小的参数把进度带到停止条件。
若把起点改成 down(2);,屏幕应显示 2 然后 1。方法体不变;只改第一个参数,所以链更短。从 down(4); 开始应显示四行:4、3、2、1。同样方法体不变;只有 main 选择第一个值。

把停止条件放在前面

停止条件必须在打印和自调用之前检查。若先打印却从不测试 n,或总是用同一个值调用 down(n),程序就不会结束。在本页的初学者形状里,把 if (n <= 0) 块放在方法顶部。

stop checked before print

java
public class Ladder {
    public static void main(String[] args) {
        down(2);
    }

    static void down(int n) {
        if (n <= 0) {
            return;
        }
        System.out.println(n);
        down(n - 1);
    }
}

再次编译并运行:javac Ladder.java,然后 java Ladder。屏幕显示 2 然后 1。当 n 到 0 时,停止条件仍会结束整条链。写成 down(n) 而不是 down(n - 1),会反复送出同一个数。那时程序可能一直跑到被系统打断。更小的参数是想法的一部分:每次调用都必须朝停止条件前进。
把停止条件想成出口门。打印是这一步的工作。自调用是下一步。没有门,每一步都会无尽地打开另一步。若下一步用同一个 n,你永远走不到门。两种错误在源码里看起来不同,但在本页初学者形状里都拒绝干净结束。

屏幕旁的调用链

对照它产生的行,读一次起始调用。每一行都是一次仍会打印的 down 访问。

📊 down call chain for down(3)

调用 n 的值 屏幕行
第一次 down(3) 3 3
然后 down(2) 2 2
然后 down(1) 1 1
然后 down(0) 0 (无行——停止)

常见混淆是漏掉停止条件。那样每次调用都会打印并再次调用——永远不停。另一种混淆是在递归调用之后打印,而不是之前。本页打印在前,所以屏幕向下倒数。若把 println 移到 down(n - 1) 下面,行顺序会变——为本页的倒数,请把打印放在自调用之上。
上一页的作用域仍然略有关系:每次调用的 n 是那次调用自己的参数。外层调用的 n 不会在内层运行时改写自己。内层有单独的 n。内层返回后,外层从自调用之后的那一行继续——而在本页,那就是方法的结尾。
若同一文件里还留着带 mark(4) 的旧 Gate 演示,且 main 仍在调用 mark,屏幕会乱。学本页时把 main 清到只剩 down(3); 或 down(2);。不要把 down 嵌进 main 的花括号里。方法写在类里、挨着 main。
在 main 里写 System.out.println("down"); 只会打印单词 down。它不会启动整条链。若要三行倒数,请保持 down(3);。

再一种形状:停止时不打印

你已见过带 0 的调用安静返回。也可以用起始值 1 练习。屏幕应显示一行:1。然后下一次是停止的 down(0)。

down starting at one

java
public class Ladder {
    public static void main(String[] args) {
        down(1);
    }

    static void down(int n) {
        if (n <= 0) {
            return;
        }
        System.out.println(n);
        down(n - 1);
    }
}

执行 javac Ladder.java 和 java Ladder 后,屏幕显示一行:1。同一方法体覆盖 down(3)、down(2) 和 down(1)。只有 main 里的第一个参数在变。对本页初学者内容已经够了。从递归返回求和等更深内容留给后面的课程;这里目标是自调用、更小的参数和停止条件。
若屏幕什么也不显示,检查 main 是否仍在调用别的东西,或停止测试是否写错——例如 if (n >= 0) 在正数 n 起步时会在任何打印前就停。保持 n <= 0 作为安静出口,并在 main 里保留正数起点,这样至少能出现一行。

进入 classes 之前

你可以写 down(int n),在 n 为 0 或更小时停止,打印 n,再调用 down(n - 1)。从 main 一次调用启动整条链。屏幕显示倒数行。从不朝停止条件前进的方法不会结束。下一页是 classes;在那里你开始在这些 static 方法页之外,用字段构建类型。

🧠 测试您的知识

准备开始

测试您的知识

通过这个互动测验挑战自己,看看你对这个主题的理解程度如何

❓
5
问题
🎯
70%
及格要求
♾️
∞
时间
🔄
∞
尝试次数

📝 说明

  • 仔细阅读每个问题
  • 为每个问题选择最佳答案
  • 您可以随时重新参加测验
  • 您的进度将显示在顶部