第5章:方法与栈
本章只讲 Java。Python 和 C++ 的函数语法在末尾统一对比。
一个故事开头
你每天做奶茶的手艺越来越熟练。
"取杯子 → 加珍珠 → 加茶底 → 加奶 → 封口 → 装袋"
你发现每个客人点的流程都差不多,无非是口味不同。于是你写了一张"波波奶茶制作流程卡",以后客人点波波奶茶的时候,你直接抽卡片照着做就行,不用每次重新想一遍。
方法(Method)就是程序的"流程卡"——把一段代码包起来,给个名字,以后随时可以拿出来用。
// 定义一张流程卡
public static void makeBubbleTea() {
System.out.println("取杯");
System.out.println("加珍珠");
System.out.println("加茶底");
System.out.println("加奶");
System.out.println("封口");
}
// 需要用的时候,喊名字就行
makeBubbleTea(); // 做一杯
makeBubbleTea(); // 再做一杯1. 为什么要用方法
// 没有方法——复制粘贴
System.out.println("取杯");
System.out.println("加珍珠");
System.out.println("加茶底");
System.out.println("加奶");
System.out.println("封口");
// 第二杯再来一遍...
System.out.println("取杯");
// ...三个问题: 代码重复、改一处要改所有地方、读起来像流水账。
// 有方法——一次定义,随意调用
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. 方法的定义和调用
基本语法
public static void 方法名() {
// 要执行的代码
}
// 调用
方法名();public static void 这串你在 main 方法上见过。第5章你还不需要完全理解 static——先当固定格式用,第7章会讲含义。
一个简单的例子
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() 方法对每个客人都说一样的——太死板了。给方法加上参数,每次调用可以传不同的值:
public static void greet(String name) {
System.out.println("你好," + name + "!欢迎光临!");
}
public static void main(String[] args) {
greet("小明"); // 你好,小明!欢迎光临!
greet("小红"); // 你好,小红!欢迎光临!
greet("小张"); // 你好,小张!欢迎光临!
}多个参数:
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. 返回值——方法也能"还"东西
有些方法不光执行动作,还要还一个结果给你。
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 改成返回值的类型(int、double、String 等),方法体里用 return 返回。
// 返回 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. 方法调用栈
这是理解"程序怎么跑"的关键概念。
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 里方法参数传递的是值的副本。你在方法里修改参数,不会影响外面的变量。
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,没变!
}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 可以根据你传的参数类型/数量自动选择调用哪个:
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. 递归——方法调用自己
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...
发射!递归的两个要素:
- 终止条件(
n == 0)——不然会无限调用自己,直到栈溢出 - 向终止条件推进(
n - 1)——每次调用都更接近终止条件
💥 拆了它:去掉终止条件会怎样
public static void infinite(int n) {
System.out.println(n);
infinite(n + 1); // 没有终止条件!
}跑一下。你最终会看到:StackOverflowError
调用栈的空间是有限的。每次方法调用都要占一块空间,无限调用就把栈塞爆了——Java 报 StackOverflowError,程序崩溃。
9. 完整例子:奶茶店计算器
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: 能解决的问题一样。循环更快(没方法调用开销),递归更优雅(如树形结构遍历)。初学阶段用循环更安全——不容易栈溢出。
你现在学会的东西
- 方法 = 流程卡——把代码包起来,给个名字,重复利用
- 方法可以有参数(输入)和返回值(输出)
- 调用栈 = 叠盘子——后调用的先返回(LIFO)
- Java 是值传递——改参数不会影响外面的变量
- 方法重载——同名不同参,Java 根据参数选
- 递归——方法调自己,必须要有终止条件
✅ 验收标准
完成本章后,你应该能:
- [ ] 定义并调用带参数和返回值的方法
- [ ] 区分值传递和引用传递
- [ ] 使用方法重载实现同名不同参
- [ ] 理解调用栈的入栈/出栈过程
- [ ] 用递归解决阶乘/斐波那契等问题
📌 常见卡点
- 返回类型写 void 却试图 return 值
- 形参和实参的概念混淆
- 递归没有终止条件导致 StackOverflowError
- 方法签名不包括返回值类型——参数列表不同才能重载
🔜 现在不需要理解
- 可变参数 `...`——后面用到再说
- 方法引用 `::`——Java 8 特性
- 尾递归优化——Java 不支持
🧪 练习
1. 写一个方法:定义一个 static int max(int a, int b) 方法,返回两个数中较大的那个。
2. 方法重载:定义两个同名方法 printInfo——一个接受 String name,另一个接受 String name 和 int age,在 main 里都调一遍。
3. 递归:用递归写一个 static int sum(int n) 方法,返回 1 到 n 的和。提示:sum(5) = 5 + sum(4),终止条件 n == 1。