主题
Chapter 06 · 方法(函数)
把"重复的事"封装成一个动作,按下按钮就执行 — 这就是方法。
🎯 本章目标
- 学会定义方法、调用方法、理解 方法签名
- 理解 参数传递:值传递的真相
- 掌握 方法重载(同名不同参)
- 学会写 递归 方法
- 知道 可变参数 varargs 怎么用
1. 方法是什么?洗衣机的故事
家里有一台洗衣机:
- 你不需要知道里面齿轮怎么转、水怎么注、电怎么走
- 你只要:"放衣服 → 按按钮 → 等结果"
- 按钮(输入) → 黑盒 → 干净衣服(输出)
这就是方法:接受一些输入,做点事情,可能返回一个结果。
java
public static int max(int a, int b) { // 输入: a, b
if (a > b) return a; // 处理
return b; // 输出
}2. 方法的解剖图
java
public static int max(int a, int b) {
↑ ↑ ↑ ↑ ↑
访问修饰 static 返回 方法名 参数列表
类型五个组成部分
| 部分 | 例子 | 作用 |
|---|---|---|
| 访问修饰符 | public | 谁能调用(后面 OOP 章节细讲) |
| 静态修饰 | static | 是否需要先 new 对象(先不管,main 里默认写 static) |
| 返回类型 | int | 结果是什么类型;没有返回写 void |
| 方法名 | max | 小驼峰命名 |
| 参数列表 | (int a, int b) | 输入,可以为空 |
方法签名 = 方法名 + 参数列表
JVM 通过 方法签名 区分两个方法。返回类型不参与签名!
java
int add(int a, int b) // 签名: add(int, int)
double add(int a, int b) // ❌ 编译错误:与上面签名相同3. 调用方法
java
public class Calc {
public static int max(int a, int b) {
return a > b ? a : b;
}
public static void greet(String name) {
System.out.println("Hello, " + name);
}
public static void main(String[] args) {
int m = max(3, 5); // 接收返回值
System.out.println(m);
greet("张三"); // 不接收返回值(void)
}
}4. 参数传递的真相:值传递
🎯 Java 只有"值传递" —— 这是面试高频考点。
基本类型:传值的副本
java
static void change(int n) {
n = 100;
}
public static void main(String[] args) {
int x = 5;
change(x);
System.out.println(x); // 还是 5!
}引用类型:传"引用的副本"
java
static void modify(int[] arr) {
arr[0] = 999; // ✅ 改原数组
arr = new int[]{1}; // ❌ 这只是改了局部副本
}
public static void main(String[] args) {
int[] a = {1, 2, 3};
modify(a);
System.out.println(a[0]); // 999
System.out.println(a.length); // 3(引用没变)
}💡 正确理解:传的是 引用变量本身的值(即对象的地址)。能改对象内容,但改不了"指向哪个对象"。
5. 方法重载(Overload)
同一个类里,方法名相同但参数列表不同,叫重载。
java
public class MathUtil {
static int max(int a, int b) { return a > b ? a : b; }
static double max(double a, double b) { return a > b ? a : b; }
static int max(int a, int b, int c) {
return max(max(a, b), c);
}
}调用时编译器根据实参类型 自动选择:
java
MathUtil.max(3, 5); // 调第 1 个
MathUtil.max(3.0, 5.0); // 调第 2 个
MathUtil.max(3, 5, 8); // 调第 3 个重载的规则
| 区分点 | 是否区分重载 |
|---|---|
| 方法名 | ✅ 必须相同 |
| 参数个数 | ✅ 不同算重载 |
| 参数类型 | ✅ 不同算重载 |
| 参数顺序 | ✅ 不同算重载(如 (int,String) vs (String,int)) |
| 返回类型 | ❌ 不区分 |
| 参数名 | ❌ 不区分 |
经典案例:System.out.println
JDK 重载了 10 个 println:传啥都能打印。
6. 递归:自己调用自己
🎯 递归 = 解决问题的方法 = 解决子问题的同样方法
经典例子:阶乘 n!
数学定义:
n! = n × (n-1)!
0! = 1代码:
java
public static int factorial(int n) {
if (n == 0) return 1; // 终止条件(必须有!)
return n * factorial(n - 1); // 递归调用
}
factorial(5);
// = 5 * factorial(4)
// = 5 * 4 * factorial(3)
// = 5 * 4 * 3 * factorial(2)
// = 5 * 4 * 3 * 2 * factorial(1)
// = 5 * 4 * 3 * 2 * 1 * factorial(0)
// = 5 * 4 * 3 * 2 * 1 * 1 = 120递归三要素
- 终止条件:什么时候停?(如
n == 0) - 递归调用:调用自己处理子问题(如
factorial(n-1)) - 每次缩小问题规模:让 n 越来越小,否则栈溢出!
经典面试题:斐波那契
java
// 递归版(简洁但慢,O(2^n))
public static long fib(int n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
// 循环版(快得多,O(n))
public static long fibIter(int n) {
if (n <= 1) return n;
long a = 0, b = 1;
for (int i = 2; i <= n; i++) {
long c = a + b;
a = b;
b = c;
}
return b;
}⚠️ 递归虽然简洁,栈空间有限,太深会
StackOverflowError。生产代码优先用循环。
7. 可变参数 varargs
需要传任意多个同类型参数?用 ...:
java
public static int sum(int... nums) { // nums 实际是 int[]
int s = 0;
for (int n : nums) s += n;
return s;
}
sum(); // 0
sum(1); // 1
sum(1, 2, 3); // 6
sum(1, 2, 3, 4, 5); // 15💡 限制:可变参数必须放在 参数列表的最后:
javavoid log(String level, Object... args) { ... } // ✅ void log(Object... args, String level) { ... } // ❌
8. 实战练习
| 文件 | 内容 |
|---|---|
MethodBasics.java | 方法定义与调用 |
OverloadDemo.java | 方法重载 |
RecursionDemo.java | 阶乘、斐波那契、汉诺塔 |
VarargsDemo.java | 可变参数 |
ParamPassingDemo.java | 参数传递实验 |
9. 浏览器演示
打开 demo.html:
- 方法调用栈可视化
- 递归过程动画
- 重载选择演示
10. 面试可能会问什么?
Q1: Java 是值传递还是引用传递?
答:只有值传递。
- 基本类型:传值的副本
- 引用类型:传 引用值的副本(即对象地址的副本)
虽然能改对象内容,但 不能改变实参指向,这正是值传递的特征。
Q2: 方法重载和重写的区别?
| 对比 | 重载 Overload | 重写 Override |
|---|---|---|
| 位置 | 同一个类 | 父类与子类 |
| 方法名 | 必须相同 | 必须相同 |
| 参数列表 | 必须不同 | 必须相同 |
| 返回类型 | 无要求 | 必须相同(或子类型) |
| 何时绑定 | 编译时(静态分派) | 运行时(动态分派) |
Q3: 递归有什么风险?
答:
- 栈溢出(StackOverflowError):递归过深
- 重复计算:如朴素的斐波那契 fib(40) 巨慢
- 可读性 vs 性能:能用循环就用循环
Q4: static 方法和实例方法的区别?
static方法:属于类,不需要创建对象- 实例方法:必须
new一个对象才能调
java
Math.max(3, 5); // static,直接调
"hello".length(); // 实例方法,需要先有对象Q5: 方法可以有同名的形参吗?
java
public void foo(int a, int a) { ... } // ❌ 编译错误不可以。形参名在方法内必须唯一。
Q6: Java 没有"默认参数",怎么实现?
答:用 方法重载 模拟:
java
public void send(String msg) { send(msg, "INFO"); }
public void send(String msg, String level) { ... }🎁 本章小结
✅ 方法 = 输入参数 + 处理逻辑 + 输出返回值
✅ 方法签名 = 方法名 + 参数列表(不含返回类型)
✅ Java 只有值传递(包括"引用值的副本")
✅ 重载:同名不同参;重写:父子覆盖
✅ 递归三要素:终止条件、递归调用、缩小规模
✅ 可变参数 ... 本质是数组,必须放最后🔗 导航
- ⬅️ 上一章:Chapter 05 · 数组
- ➡️ 下一章:Chapter 07 · 类与对象