第 10 章:方法定义、调用与重载
学习目标
- 掌握方法的定义、调用、参数传递
- 理解方法重载(Overload)
- 学会递归
- 理解"值传递"的本质
一、什么是方法?
方法(Method) = 一段有名字的、可重复调用的代码块。
为什么要用方法?
二、方法定义
2.1 基本语法
java
修饰符 返回类型 方法名(参数列表) {
方法体
return 返回值; // 如果返回类型不是 void
}2.2 简单示例
java
/**
* 计算两个整数的和
*
* @param a 第一个加数
* @param b 第二个加数
* @return 两数之和
*/
public static int add(int a, int b) { // ① 修饰符 + 返回类型 + 方法名 + 参数
return a + b; // ② 返回值
}2.3 方法签名
方法签名 = 方法名 + 参数列表(不包括返回类型和修饰符)
java
// 以下三个方法是不同的(方法签名不同)
public void print(int x) { }
public void print(String s) { }
public void print(int x, int y) { }方法签名是区分不同方法的唯一依据,返回类型不影响签名。
2.4 无返回值:void
java
public static void printHello() { // ① void 表示无返回值
System.out.println("Hello");
// 没有 return 或 return 后无值
}三、方法调用
3.1 静态方法调用
java
public class Calculator {
public static int add(int a, int b) { // ① static 方法
return a + b;
}
public static void main(String[] args) {
int sum = add(3, 5); // ② 同类直接调用
System.out.println("sum = " + sum);
}
}3.2 不同类的静态方法调用
java
public class MathUtils {
public static int max(int a, int b) {
return a > b ? a : b;
}
}
public class Test {
public static void main(String[] args) {
int m = MathUtils.max(10, 20); // ① 通过类名调用
System.out.println(m); // 20
}
}3.3 实例方法调用(第 14 章详解)
java
public class UserService {
public String greet(String name) { // ① 实例方法(无 static)
return "Hello, " + name;
}
}
public class Test {
public static void main(String[] args) {
UserService service = new UserService(); // ① 创建对象
String result = service.greet("张三"); // ② 通过对象调用
System.out.println(result);
}
}四、方法参数
4.1 形参 vs 实参
java
public static int add(int a, int b) { // ① a, b 是形参(形式参数)
return a + b;
}
public static void main(String[] args) {
int x = 3, y = 5;
int result = add(x, y); // ② x, y 是实参(实际参数)
}4.2 Java 只有值传递(重要!)
java
public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}
public static void main(String[] args) {
int x = 10, y = 20;
swap(x, y);
System.out.println("x = " + x + ", y = " + y); // x = 10, y = 20(没变!)
}为什么没变?
Java 把实参的值复制一份传给形参,方法内的修改只影响副本:
4.3 引用类型也是值传递
java
public static void modify(User user) { // ① 接收引用副本
user.setName("李四"); // ② 修改对象内容生效
user = null; // ③ 修改引用副本,不影响外面
}
User u = new User("张三");
modify(u);
System.out.println(u.getName()); // 李四(对象被修改了)
System.out.println(u); // 非 null(引用没被改)关键结论:Java 永远是值传递,但引用类型的"值"是地址,所以可以修改对象内容。
五、方法重载(Overload)
同一个类中,方法名相同但参数列表不同。
java
public class Calculator {
// 两个 int 相加
public int add(int a, int b) {
return a + b;
}
// 三个 int 相加(参数个数不同)
public int add(int a, int b, int c) {
return a + b + c;
}
// 两个 double 相加(参数类型不同)
public double add(double a, double b) {
return a + b;
}
// 一个 int,一个 String(顺序不同也算重载)
public String add(int a, String b) {
return a + b;
}
}5.1 重载的判定规则
方法签名不同 = 方法重载。返回值类型、修饰符不影响重载:
java
// ✅ 合法重载(参数列表不同)
public void print(int x) { }
public void print(String s) { }
public void print(int x, int y) { }
// ❌ 不是重载(参数列表相同)
// public int print(int x) { return x; } // 与上面第一个 print(int x) 冲突
// public void print(int x) { } // 编译报错5.2 编译器如何选择重载方法?
java
public static void main(String[] args) {
Calculator calc = new Calculator();
calc.add(1, 2); // 调用 add(int, int)
calc.add(1.0, 2.0); // 调用 add(double, double)
calc.add(1, 2, 3); // 调用 add(int, int, int)
}匹配优先级:精确匹配 > 自动类型转换 > 装箱 > 可变参数
java
public void test(int x) { System.out.println("int"); }
public void test(long x) { System.out.println("long"); }
public void test(Integer x) { System.out.println("Integer"); }
test(10); // 调用 test(int),因为是精确匹配六、递归
方法调用自身。
java
/**
* 计算阶乘 n! = n × (n-1) × ... × 2 × 1
*/
public static int factorial(int n) {
if (n <= 1) { // ① 递归终止条件(必须)
return 1;
}
return n * factorial(n - 1); // ② 递归调用
}
factorial(5); // 5! = 5×4×3×2×1 = 1206.1 递归的两个要素
6.2 斐波那契数列
java
/**
* 斐波那契:F(1)=1, F(2)=1, F(n)=F(n-1)+F(n-2)
*/
public static int fib(int n) {
if (n <= 2) return 1;
return fib(n - 1) + fib(n - 2);
}6.3 递归的缺点与优化
java
// ❌ 效率低:fib(40) 会有指数级调用
fib(40); // 几秒都算不完
// ✅ 优化:用循环
public static int fibIterative(int n) {
if (n <= 2) return 1;
int a = 1, b = 1, c;
for (int i = 3; i <= n; i++) {
c = a + b;
a = b;
b = c;
}
return b;
}企业级原则:能用循环就不要用递归(除非逻辑天然适合递归,如树遍历)。
七、方法设计原则
7.1 单一职责
java
// ❌ 反例:一个方法做多件事
public void processUser() {
validate();
saveToDb();
sendEmail();
log();
}
// ✅ 正例:拆分
public void createUser(User user) {
validateUser(user);
saveUser(user);
notifyUserCreated(user);
}7.2 短小精悍
java
// ❌ 反例:方法 200 行
public void process() {
// 100 行
}
// ✅ 正例:拆分为多个小方法
public void process() {
step1();
step2();
step3();
}阿里规约:单个方法不超过 80 行。
7.3 命名规范
- 方法名 lowerCamelCase
- 动词或动词短语:
saveUser、findById、calculateTotal - 布尔方法:
is/has/can前缀:isActive、hasPermission
八、完整代码示例
java
/**
* 方法综合示例
*
* 配套文档:第 10 章 方法定义、调用与重载
* 运行方式:javac MethodDemo.java && java MethodDemo
*/
public class MethodDemo {
// ============ 1. 方法重载 ============
public static int add(int a, int b) {
return a + b;
}
public static int add(int a, int b, int c) { // ① 参数个数不同
return a + b + c;
}
public static double add(double a, double b) { // ② 参数类型不同
return a + b;
}
// ============ 2. 递归:阶乘 ============
public static long factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
// ============ 3. 递归:斐波那契 ============
public static long fib(int n) {
if (n <= 2) return 1;
return fib(n - 1) + fib(n - 2);
}
// ============ 4. 数组求和 ============
public static int sum(int[] arr) {
int total = 0;
for (int num : arr) {
total += num;
}
return total;
}
// ============ 5. 可变参数(第 11 章详解)============
public static int sumVarargs(int... numbers) {
int total = 0;
for (int num : numbers) {
total += num;
}
return total;
}
// ============ 6. 主方法 ============
public static void main(String[] args) {
// 调用重载方法
System.out.println("=== 方法重载 ===");
System.out.println("add(1, 2) = " + add(1, 2));
System.out.println("add(1, 2, 3) = " + add(1, 2, 3));
System.out.println("add(1.5, 2.5) = " + add(1.5, 2.5));
// 递归
System.out.println("\n=== 递归 ===");
System.out.println("5! = " + factorial(5)); // 120
System.out.println("fib(10) = " + fib(10)); // 55
// 数组求和
System.out.println("\n=== 数组求和 ===");
int[] scores = {85, 92, 78, 95, 88};
System.out.println("总分 = " + sum(scores)); // 438
// 可变参数
System.out.println("\n=== 可变参数 ===");
System.out.println("sumVarargs(1, 2, 3) = " + sumVarargs(1, 2, 3));
System.out.println("sumVarargs(1, 2, 3, 4, 5) = " + sumVarargs(1, 2, 3, 4, 5));
// 值传递演示
System.out.println("\n=== 值传递 ===");
int x = 10, y = 20;
swap(x, y);
System.out.println("x = " + x + ", y = " + y); // 10, 20(没变)
}
public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
// 这里 a, b 交换了,但外面的 x, y 不受影响
}
}运行结果:
=== 方法重载 ===
add(1, 2) = 3
add(1, 2, 3) = 6
add(1.5, 2.5) = 4.0
=== 递归 ===
5! = 120
fib(10) = 55
=== 数组求和 ===
总分 = 438
=== 可变参数 ===
sumVarargs(1, 2, 3) = 6
sumVarargs(1, 2, 3, 4, 5) = 15
=== 值传递 ===
x = 10, y = 20九、常见面试题
Q1: 方法重载 vs 方法重写的区别?
| 重载(Overload) | 重写(Override) | |
|---|---|---|
| 发生位置 | 同一个类 | 子类继承父类 |
| 方法签名 | 不同 | 相同 |
| 返回类型 | 可以不同 | 必须相同或协变 |
| 修饰符 | 无限制 | 不能更严格 |
| 发生时期 | 编译期 | 运行期 |
Q2: Java 真的是值传递吗?
是的。引用类型的"值"是引用地址,所以可以通过引用修改对象,但不能修改外部引用本身。
Q3: 递归 vs 循环?
| 递归 | 循环 | |
|---|---|---|
| 代码简洁 | ✅ | ❌ |
| 性能 | ❌(栈开销) | ✅ |
| 内存 | ❌(栈溢出风险) | ✅ |
| 适用 | 树、图、分治 | 大多数场景 |
十、本章小结
| 要点 | 关键 |
|---|---|
| 方法的本质 | 一段有名字、可重复调用的代码块 |
| 方法签名 | 方法名 + 参数列表(不含返回类型) |
| 重载规则 | 同类同名,参数列表不同 |
| 值传递 | Java 永远是值传递,引用类型传的是地址副本 |
| 递归 | 终止条件 + 递归公式,但能用循环就别用递归 |
| 设计原则 | 单一职责、短小精悍、命名规范 |
动手练习
练习 1:基础题
写一个 MathUtils 类,实现:
java
public static int max(int a, int b) // 两数最大值
public static int max(int a, int b, int c) // 三数最大值(重载)
public static double max(double a, double b) // 重载练习 2:进阶题
用递归实现整数反转:
java
public static int reverse(int n)
// reverse(123) = 321
// reverse(-123) = -321
// reverse(1200) = 21练习 3:挑战题
不用任何临时变量,交换两个 int 的值(提示:用加减法或异或,第 7 章学过)。
推荐阅读
- 📖 《Java 核心技术》卷 1 第 3.7 节 — 方法详解
- 📖 《Effective Java》第 5 章 — Item 49 参数检验
下一章:第 11 章:可变参数与命令行参数 →