- 0
- 506 words
开篇引子——为什么要有方法?
我们的代码一直在 main 方法里塞,但所有逻辑都挤在一起是什么体验?
public static void main(String[] args) {
// 1. 输入学生成绩
System.out.println("请输入学生信息...");
// 2. 计算总成绩
int sum = 0;
// 50 行计算...
// 3. 计算平均分
// 20 行计算...
// 4. 排序
// 30 行排序...
// 5. 打印结果
// 10 行打印...
// main 方法 100+ 行,看得眼睛花
}
这就是没有方法的后果——所有代码平铺在一堆,可读性差、没法复用、改一个地方要通读全文。方法就是给一段代码取个名字,需要的时候调用就行。
一、方法的定义
1.1 基本语法
[修饰符] 返回值类型 方法名([参数类型 参数名, ...]) {
// 方法体
return 返回值; // 如果返回值类型不是 void,必须 return
}
来一个最精简的例子:
public class MethodDemo {
public static void main(String[] args) {
// 调用方法
int result = add(10, 20);
System.out.println("结果:" + result); // 30
}
// 定义一个两数相加的方法
public static int add(int a, int b) {
return a + b;
}
}
1.2 方法的结构拆解
| 组成部分 | 说明 | 例子 |
|---|---|---|
| 修饰符 | 控制访问权限和特性 | public static |
| 返回值类型 | 方法返回的数据类型 | int、String、void |
| 方法名 | 遵循小驼峰命名 | add、getMax |
| 参数列表 | 调用时传入的数据 | (int a, int b) |
| 方法体 | 花括号里的代码逻辑 | { return a + b; } |
| return | 结束方法并返回值 | return result; |
1.3 三种常见方法模式
// 1. 有参数有返回值
public static int max(int a, int b) {
return a > b ? a : b;
}
// 2. 有参数无返回值(void)
public static void printInfo(String name, int age) {
System.out.println("姓名:" + name + ",年龄:" + age);
// 没有 return 也可以,有的话 return; 只是提前结束
}
// 3. 无参数无返回值
public static void showMenu() {
System.out.println("1. 添加学生");
System.out.println("2. 查询成绩");
System.out.println("3. 退出系统");
}
一句话记:void 表示”只管做不返回”,其他类型都”做完了给我个结果”。
二、方法的重载(Overload)
2.1 什么是重载?
同一个类中,方法名相同,但参数列表不同。这就是重载。
最经典的重载例子:
public class OverloadDemo {
// 两个 int 相加
public static int add(int a, int b) {
return a + b;
}
// 三个 int 相加
public static int add(int a, int b, int c) {
return a + b + c;
}
// double 相加
public static double add(double a, double b) {
return a + b;
}
public static void main(String[] args) {
System.out.println(add(1, 2)); // 调用版本1
System.out.println(add(1, 2, 3)); // 调用版本2
System.out.println(add(1.5, 2.5)); // 调用版本3
}
}
2.2 重载的规则
- 方法名必须相同——不然不叫重载
- 参数列表必须不同——可以是类型不同、个数不同、顺序不同
- 与返回值类型无关——只有返回值不同的话,不是重载,编译报错
- 与参数名无关——
add(int a)和add(int b)是同一个方法
// ✅ 合法重载
void print(int a) {}
void print(double a) {} // 类型不同
void print(int a, int b) {} // 个数不同
void print(String s, int a) {} // 类型和顺序都不同
// ❌ 不是重载
int calc(int a) { return a; }
void calc(int b) {} // 编译报错:与上面的方法重复
2.3 重载的实际应用
System.out.println() 本身就是重载的绝佳例子——它重载了十几个版本,接收各种类型:
System.out.println(100); // println(int)
System.out.println(3.14); // println(double)
System.out.println("Hello"); // println(String)
System.out.println(true); // println(boolean)
// 全是同一个方法名,编译器根据实参自动匹配
重载让我们可以用同一个名字描述同一类操作(都是”打印”、”相加”),只是针对不同类型或数量的参数提供不同实现。这是易用性的核心。
三、方法的值传递(Pass-by-Value)
3.1 Java 只有值传递
这是 Java 新人最容易搞混的概念。网上关于”Java 到底是值传递还是引用传递”的争论可以塞满一个论坛——结论:Java 只有值传递。
先看基本类型:
public static void main(String[] args) {
int x = 10;
change(x);
System.out.println(x); // 还是 10,不是 100!
}
public static void change(int a) {
a = 100; // 改的是 a 的副本,不影响外面的 x
}
基本类型传的是值的副本,方法内部的修改不影响原变量。
3.2 引用类型的值传递
引用类型传的是”引用的副本“,听起来绕,看代码:
public static void main(String[] args) {
int[] arr = {1, 2, 3};
changeArray(arr);
System.out.println(Arrays.toString(arr)); // [99, 2, 3] —— 变了!
}
public static void changeArray(int[] a) {
a[0] = 99; // 通过引用的副本修改了同一个对象
}
但如果你试图让引用指向一个新对象:
public static void main(String[] args) {
int[] arr = {1, 2, 3};
newArray(arr);
System.out.println(Arrays.toString(arr)); // 还是 [1, 2, 3]!
}
public static void newArray(int[] a) {
a = new int[]{100, 200, 300}; // ❌ 改的是副本的指向,原数组不受影响
}
一句话总结:修改参数”指向的对象”的内容,会影响外部;修改参数”指向哪个对象”,不影响外部。
💡 当年我学这节的时候,觉得”值传递和引用传递”是个语言哲学问题。后来面试官直接问了句:Java 方法的参数能改成另一个对象吗?—— 不能。为什么?值传递。简洁明了。
四、方法的递归
4.1 递归是什么
递归就是方法自己调自己。听起来像死循环,但关键是:每次调用都让问题规模缩小,最终到达一个”可以直接解决”的终止条件。
4.2 经典案例:计算阶乘
// 5! = 5 × 4 × 3 × 2 × 1 = 120
// 数学定义:n! = n * (n-1)!, 且 0! = 1, 1! = 1
public static int factorial(int n) {
// 终止条件(核心!没有它就会 StackOverflow)
if (n == 0 || n == 1) {
return 1;
}
// 递归步骤:问题规模缩小
return n * factorial(n - 1);
}
// 调用
System.out.println(factorial(5)); // 120
执行过程拆解:
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 ← 触底反弹(return)
→ 5 * 4 * 3 * 2
→ 5 * 4 * 6
→ 5 * 24
→ 120
4.3 斐波那契数列
// 斐波那契:1, 1, 2, 3, 5, 8, 13, 21...
// 定义:f(1)=1, f(2)=1, f(n)=f(n-1)+f(n-2)
public static int fib(int n) {
if (n == 1 || n == 2) {
return 1;
}
return fib(n - 1) + fib(n - 2);
}
System.out.println(fib(6)); // 8
⚠️ 性能警告:上面这个递归版斐波那契效率极低——
fib(50)跑到天荒地老,因为大量重复计算。后面会学到用循环或记忆化递归来优化。递归不是万能的,要判断是否值得。
4.4 递归的核心三要素
- 终止条件:必须有明确的结束条件,否则栈溢出
- 递归步骤:每次调用都要让问题规模缩小
- 问题能够分解:大问题能拆成小问题的子问题,且解法相同
能用递归解决的问题通常也可以用循环解决。递归的优点是代码写起来”数学感”强、清晰,缺点是性能不如循环、容易栈溢出。递归深度超过几千层就要小心了。
五、综合示例:方法改造学生成绩系统
现在我们有方法了,可以把之前的代码模块化:
import java.util.Arrays;
public class ScoreSystemV2 {
public static void main(String[] args) {
int[] scores = {76, 88, 62, 95, 81};
printArray("原始成绩", scores);
int sum = sum(scores);
double avg = avg(scores);
int max = max(scores);
int min = min(scores);
System.out.println("总分:" + sum);
System.out.println("平均分:" + avg);
System.out.println("最高分:" + max);
System.out.println("最低分:" + min);
// 递归版阶乘(做个展示)
System.out.println("5! = " + factorial(5));
}
// 工具方法
public static void printArray(String label, int[] arr) {
System.out.println(label + ":" + Arrays.toString(arr));
}
public static int sum(int[] arr) {
int total = 0;
for (int n : arr) total += n;
return total;
}
public static double avg(int[] arr) {
return (double) sum(arr) / arr.length; // 复用 sum()
}
public static int max(int[] arr) {
int max = arr[0];
for (int n : arr) if (n > max) max = n;
return max;
}
public static int min(int[] arr) {
int min = arr[0];
for (int n : arr) if (n < min) min = n;
return min;
}
// 递归
public static int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
}
对比一下——main 方法只有 11 行,每个功能有单独的方法。这就是模块化编程的魅力:清晰、可复用、好维护。
总结
- 方法是代码的”积木块”,有修饰符、返回值、方法名、参数列表和方法体
- 重载让同名方法处理不同类型/数量的参数,典型如
println() - 值传递:Java 只有值传递。基本类型传副本,引用类型传引用副本——修改引用指向对象的内容会反映到外部
- 递归是方法自调用,必须有终止条件。代码简洁但性能稍差,深度大时慎用
下一篇我们将做数组与方法的综合练习,刷几道实在的题目巩固这些知识点。