Skip to content

第5章:方法与栈

本章只讲 Java。Python 和 C++ 的函数语法在末尾统一对比。

你将学会
方法定义·调用·递归
前置知识
第4章 控制流
预计时间
35 分钟
需要准备
JDK + 编辑器
完成标志
自己封装方法并理解调用栈

一个故事开头

你每天做奶茶的手艺越来越熟练。

"取杯子 → 加珍珠 → 加茶底 → 加奶 → 封口 → 装袋"

你发现每个客人点的流程都差不多,无非是口味不同。于是你写了一张"波波奶茶制作流程卡",以后客人点波波奶茶的时候,你直接抽卡片照着做就行,不用每次重新想一遍。

方法(Method)就是程序的"流程卡"——把一段代码包起来,给个名字,以后随时可以拿出来用。

java
// 定义一张流程卡
public static void makeBubbleTea() {
    System.out.println("取杯");
    System.out.println("加珍珠");
    System.out.println("加茶底");
    System.out.println("加奶");
    System.out.println("封口");
}

// 需要用的时候,喊名字就行
makeBubbleTea();    // 做一杯
makeBubbleTea();    // 再做一杯

1. 为什么要用方法

java
// 没有方法——复制粘贴
System.out.println("取杯");
System.out.println("加珍珠");
System.out.println("加茶底");
System.out.println("加奶");
System.out.println("封口");
// 第二杯再来一遍...
System.out.println("取杯");
// ...

三个问题: 代码重复、改一处要改所有地方、读起来像流水账。

java
// 有方法——一次定义,随意调用
public static void makeBubbleTea() {
    System.out.println("取杯");
    System.out.println("加珍珠");
    System.out.println("加茶底");
    System.out.println("加奶");
    System.out.println("封口");
}

public static void main(String[] args) {
    makeBubbleTea();  // 客人A
    makeBubbleTea();  // 客人B
}

这就是方法的核心价值:封装一段逻辑,给个名字,重复利用。


2. 方法的定义和调用

基本语法

java
public static void 方法名() {
    // 要执行的代码
}

// 调用
方法名();

public static void 这串你在 main 方法上见过。第5章你还不需要完全理解 static——先当固定格式用,第7章会讲含义。

一个简单的例子

java
public class MethodDemo {
    public static void main(String[] args) {
        System.out.println("开始营业");
        greet();            // 调用方法
        System.out.println("结束营业");
    }
    
    // 定义一个打招呼的方法
    public static void greet() {
        System.out.println("你好!欢迎光临!");
        System.out.println("今天有特价活动哦!");
    }
}

输出:

开始营业
你好!欢迎光临!
今天有特价活动哦!
结束营业

执行流程: main 方法执行到 greet() 时,跳转到 greet 方法,执行完 greet 里的代码,再跳回 main 继续往下走。


3. 参数——让方法更灵活

上面的 greet() 方法对每个客人都说一样的——太死板了。给方法加上参数,每次调用可以传不同的值:

java
public static void greet(String name) {
    System.out.println("你好," + name + "!欢迎光临!");
}

public static void main(String[] args) {
    greet("小明");   // 你好,小明!欢迎光临!
    greet("小红");   // 你好,小红!欢迎光临!
    greet("小张");   // 你好,小张!欢迎光临!
}

多个参数:

java
public static void order(String drink, int quantity) {
    System.out.println("已下单:" + drink + " × " + quantity);
}

public static void main(String[] args) {
    order("波波奶茶", 2);  // 已下单:波波奶茶 × 2
    order("柠檬茶", 1);    // 已下单:柠檬茶 × 1
}

参数 = 方法的"输入"。传什么值,方法就按什么值来执行。


4. 返回值——方法也能"还"东西

有些方法不光执行动作,还要还一个结果给你。

java
public static double calculateTotal(double price, int quantity) {
    double total = price * quantity;
    return total;  // 把结果返回给调用者
}

public static void main(String[] args) {
    double result = calculateTotal(15.0, 3);  // result = 45.0
    System.out.println("总价:" + result);
    
    // 也可以直接用,不存变量
    System.out.println(calculateTotal(18.0, 2));  // 36.0
}

void 表示不返回任何东西。要返回值的话,把 void 改成返回值的类型(intdoubleString 等),方法体里用 return 返回。

java
// 返回 int
public static int add(int a, int b) {
    return a + b;
}

// 返回 boolean
public static boolean isAdult(int age) {
    return age >= 18;
}

// 返回 String
public static String makeGreeting(String name) {
    return "你好," + name + "!";
}

5. 方法调用栈

这是理解"程序怎么跑"的关键概念。

java
public static void a() {
    System.out.println("进入 a");
    b();
    System.out.println("离开 a");
}

public static void b() {
    System.out.println("进入 b");
    c();
    System.out.println("离开 b");
}

public static void c() {
    System.out.println("进入 c");
}

public static void main(String[] args) {
    System.out.println("开始");
    a();
    System.out.println("结束");
}

输出:

开始
进入 a
进入 b
进入 c
离开 b
离开 a
结束

调用栈(Call Stack)就像叠盘子:

main()         → 放一个盘子
main() → a()   → 在 main 上面放一个盘子
main() → a() → b()   → 再放一个
main() → a() → b() → c()  → 最上面
main() → a() → b()        → c 执行完,拿走它的盘子
main() → a()              → b 执行完,拿走
main()                     → a 执行完,拿走
结束                        → main 执行完

后调用的方法先返回,先调用的后返回。 这叫"后进先出"(LIFO)。


6. 值传递

Java 里方法参数传递的是值的副本。你在方法里修改参数,不会影响外面的变量。

java
public static void changeValue(int x) {
    x = 100;  // 改的是副本
}

public static void main(String[] args) {
    int num = 10;
    changeValue(num);       // 传的是 num 的值(10)的副本
    System.out.println(num);  // 还是 10,没变!
}
java
public static void main(String[] args) {
    int a = 5;
    int b = a;   // 把 a 的值复制给 b
    b = 100;     // 改 b 不会影响 a
    System.out.println(a);  // 5
}

7. 方法重载——同名不同参

同一个方法名,参数不同,Java 可以根据你传的参数类型/数量自动选择调用哪个:

java
public static void printInfo(String name) {
    System.out.println("名字:" + name);
}

public static void printInfo(String name, int age) {
    System.out.println("名字:" + name + ",年龄:" + age);
}

public static void printInfo(String name, double score) {
    System.out.println("名字:" + name + ",分数:" + score);
}

public static void main(String[] args) {
    printInfo("小明");                      // 调第1个
    printInfo("小红", 20);                  // 调第2个
    printInfo("小张", 95.5);                // 调第3个
}

Java 根据参数的数量和类型来区分调哪个方法。 这在写工具方法时非常有用。


8. 递归——方法调用自己

java
public static void countdown(int n) {
    if (n == 0) {
        System.out.println("发射!");
        return;     // 结束方法
    }
    System.out.println(n + "...");
    countdown(n - 1);  // 方法自己调自己
}

public static void main(String[] args) {
    countdown(5);
}

输出:

5...
4...
3...
2...
1...
发射!

递归的两个要素:

  1. 终止条件n == 0)——不然会无限调用自己,直到栈溢出
  2. 向终止条件推进n - 1)——每次调用都更接近终止条件

💥 拆了它:去掉终止条件会怎样

java
public static void infinite(int n) {
    System.out.println(n);
    infinite(n + 1);  // 没有终止条件!
}

跑一下。你最终会看到:StackOverflowError

调用栈的空间是有限的。每次方法调用都要占一块空间,无限调用就把栈塞爆了——Java 报 StackOverflowError,程序崩溃。


9. 完整例子:奶茶店计算器

java
public class MilkTeaCalculator {
    // 计算单杯价格
    public static double getPrice(String drink) {
        return switch (drink) {
            case "波波奶茶" -> 15.0;
            case "杨枝甘露" -> 18.0;
            case "柠檬茶" -> 12.0;
            case "抹茶拿铁" -> 16.0;
            default -> 0.0;
        };
    }
    
    // 计算总价
    public static double calculateTotal(String drink, int quantity, boolean isMember) {
        double price = getPrice(drink);
        double subtotal = price * quantity;
        if (isMember) {
            subtotal *= 0.85;  // 会员85折
        }
        return subtotal;
    }
    
    // 判断满减
    public static boolean hasDiscount(double total) {
        return total >= 30;
    }
    
    // 输出订单
    public static void printReceipt(String drink, int quantity, double total) {
        System.out.println("===== 团子奶茶店 =====");
        System.out.println(drink + " × " + quantity);
        System.out.println("总价:" + total + "元");
        if (hasDiscount(total)) {
            double finalPrice = total - 5;
            System.out.println("满减优惠!实付:" + finalPrice + "元");
        }
        System.out.println("======================");
    }
    
    public static void main(String[] args) {
        String drink = "波波奶茶";
        int qty = 3;
        boolean member = true;
        
        double total = calculateTotal(drink, qty, member);
        printReceipt(drink, qty, total);
    }
}

我猜你会问

Q: 方法可以返回多个值吗? A: Java 方法只能返回一个值。想返回多个的话,可以返回数组、对象,或者用后面章节会讲的 Record。

Q: main 方法为什么必须是 public static void A: public 让 JVM 能访问它,static 让 JVM 可以不创建对象直接调用,void 是 main 结束后没什么需要返回给 JVM 的。这是 Java 的固定规则。

Q: 递归和循环哪个好? A: 能解决的问题一样。循环更快(没方法调用开销),递归更优雅(如树形结构遍历)。初学阶段用循环更安全——不容易栈溢出。


你现在学会的东西

  1. 方法 = 流程卡——把代码包起来,给个名字,重复利用
  2. 方法可以有参数(输入)和返回值(输出)
  3. 调用栈 = 叠盘子——后调用的先返回(LIFO)
  4. Java 是值传递——改参数不会影响外面的变量
  5. 方法重载——同名不同参,Java 根据参数选
  6. 递归——方法调自己,必须要有终止条件

✅ 验收标准

完成本章后,你应该能:

  • [ ] 定义并调用带参数和返回值的方法
  • [ ] 区分值传递和引用传递
  • [ ] 使用方法重载实现同名不同参
  • [ ] 理解调用栈的入栈/出栈过程
  • [ ] 用递归解决阶乘/斐波那契等问题

📌 常见卡点

  • 返回类型写 void 却试图 return 值
  • 形参和实参的概念混淆
  • 递归没有终止条件导致 StackOverflowError
  • 方法签名不包括返回值类型——参数列表不同才能重载

🔜 现在不需要理解

  • 可变参数 `...`——后面用到再说
  • 方法引用 `::`——Java 8 特性
  • 尾递归优化——Java 不支持


🧪 练习

1. 写一个方法:定义一个 static int max(int a, int b) 方法,返回两个数中较大的那个。

2. 方法重载:定义两个同名方法 printInfo——一个接受 String name,另一个接受 String nameint age,在 main 里都调一遍。

3. 递归:用递归写一个 static int sum(int n) 方法,返回 1 到 n 的和。提示:sum(5) = 5 + sum(4),终止条件 n == 1


下一篇

第6章 数组

用 ❤️ 构建 | Software Systems Atlas