目录
写在前面
一、方法是什么?用生活场景理解
二、方法的定义与使用
2.1 基本语法
2.2 方法定义的注意事项
三、方法的调用
方法的执行过程
实战:用方法简化阶乘求和
四、形参和实参
4.1 基本概念
4.2 一个经典问题:为什么交换不了?
五、方法重载
5.1 为什么需要重载?
5.2 重载的概念
5.3 重载的规则
5.4 方法签名
六、递归:方法调用自身
6.1 递归的思想
6.2 递归的两个必要条件
6.3 代码实现:递归求阶乘
6.4 递归的执行过程
6.5 递归练习
七、总结
写在前面
经过前面几篇文章的学习,我们已经掌握了变量、运算符、流程控制和输入输出。但不知你有没有发现一个问题:当我们的程序越来越复杂时,代码变得越来越臃肿,同样的逻辑反复出现,修改一处就要改动多处。
比如我们要计算1!、2!、3!……5!,如果不使用方法,就得把阶乘的逻辑重复写五遍。这不仅浪费时间,还容易出错。
今天这篇文章,我们来学习Java中最重要的代码组织方式——方法。掌握了方法,你的代码将从"流水账"升级为"模块化"。
一、方法是什么?用生活场景理解
想象一下你在家里制作咖啡。整个过程大致是这样的:
准备材料:咖啡豆、水、糖、牛奶
磨咖啡豆:将咖啡豆磨成粉
煮水:将水煮沸
冲泡:将热水倒入咖啡粉中,浸泡几分钟
添加配料:根据个人喜好添加糖或牛奶
享用:倒入杯中,品尝美味的咖啡
每次想喝咖啡都要重复这一整套流程,是不是很麻烦?但如果有一个"一键制作咖啡"的机器呢?你只需要放入材料(参数),按下按钮(调用),稍等片刻就能得到一杯咖啡(返回值)。
Java中的方法(method)就是这个道理。 它是一段代码的集合,用来完成某个特定的功能。有了方法,我们就不用每次都从头写一遍相同的逻辑,直接"调用"就行了。
二、方法的定义与使用
2.1 基本语法
修饰符 返回值类型 方法名称(参数类型 形参...) { 方法体代码; [return 返回值]; }来看一个具体的例子——判断闰年:
public static boolean isLeapYear(int year) { if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) { return true; } else { return false; } }再看一个加法方法:
public static int add(int x, int y) { return x + y; }2.2 方法定义的注意事项
修饰符:现阶段统一使用
public static固定搭配返回值类型:有返回值时,类型必须与返回的实体一致;没有返回值时,写成
void方法名:采用小驼峰命名法,如
isLeapYear、calculateSum参数列表:没有参数就留空,多个参数用逗号隔开
方法体:方法内部要执行的语句
方法必须写在类中,不能嵌套定义
Java中没有方法声明一说,定义即声明
三、方法的调用
方法写好了,但它不会自动执行。只有被调用时才会运行。
public class MethodDemo { public static void main(String[] args) { // 调用方法 int result = add(10, 20); System.out.println("10 + 20 = " + result); boolean flag = isLeapYear(2024); System.out.println("2024年是闰年吗?" + flag); } public static int add(int x, int y) { return x + y; } public static boolean isLeapYear(int year) { return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; } }调用规则总结:
定义方法时,代码不会执行,只有调用时才执行
调用时使用
方法名(参数)的形式如果方法有返回值,需要用变量接收
方法的执行过程
当方法被调用时,程序会跳转到方法内部执行,执行完毕后回到调用处继续往下走。
public static void main(String[] args) { System.out.println("第一次调用之前"); int ret = add(10, 20); System.out.println("第一次调用之后,ret=" + ret); System.out.println("第二次调用之前"); ret = add(30, 50); System.out.println("第二次调用之后,ret=" + ret); } public static int add(int x, int y) { System.out.println("调用方法中,x=" + x + " y=" + y); return x + y; }执行结果:
第一次调用之前 调用方法中,x=10 y=20 第一次调用之后,ret=30 第二次调用之前 调用方法中,x=30 y=50 第二次调用之后,ret=80可以看到,同一个方法可以被多次调用,每次传入不同的参数,得到不同的结果。
实战:用方法简化阶乘求和
回想一下我们在循环章节写的1!+2!+...+5!的代码,当时用了嵌套循环。现在有了方法,代码会清爽很多:
public static void main(String[] args) { int sum = 0; for (int i = 1; i <= 5; i++) { sum += factorial(i); } System.out.println("sum = " + sum); } public static int factorial(int n) { int result = 1; for (int i = 1; i <= n; i++) { result *= i; } return result; }执行结果:
sum = 153对比之前的嵌套循环,用方法把阶乘的计算封装起来,主逻辑变得清晰易懂。这就是方法的核心价值——模块化组织代码。
四、形参和实参
4.1 基本概念
形参:方法定义时括号里的参数,相当于数学函数中的自变量
实参:方法调用时实际传入的值
public static int add(int x, int y) { // x和y是形参 return x + y; } // 调用时 int result = add(10, 20); // 10和20是实参4.2 一个经典问题:为什么交换不了?
来看这段代码:
public static void main(String[] args) { int a = 10; int b = 20; System.out.println("交换前:a=" + a + " b=" + b); swap(a, b); System.out.println("交换后:a=" + a + " b=" + b); } public static void swap(int x, int y) { int temp = x; x = y; y = temp; }运行结果:
交换前:a=10 b=20 交换后:a=10 b=20明明在swap方法里交换了,为什么a和b的值没变?
原因分析: 实参a和b是main方法中的变量,存储在main方法的栈空间中;形参x和y是swap方法中的变量,存储在swap方法的栈空间中。两者是完全独立的实体。调用swap时,只是把a和b的值拷贝了一份给x和y,对x和y的操作自然不会影响到a和b。
一句话总结:实参的值永远都是拷贝给形参,形参和实参是两个不同的实体。
五、方法重载
5.1 为什么需要重载?
假设我们想写一个加法方法,既要能加整数,又要能加小数。按照之前的思路,可能需要两个不同的方法名:
public static int addInt(int x, int y) { ... } public static double addDouble(double x, double y) { ... }但取名本身就是一件麻烦事。能不能都用同一个名字呢?
5.2 重载的概念
方法重载:在同一个类中,多个方法名字相同,但参数列表不同。
public static int add(int x, int y) { return x + y; } public static double add(double x, double y) { return x + y; } public static int add(int x, int y, int z) { return x + y + z; }调用时,编译器会根据实参的类型和个数,自动匹配最合适的方法:
int r1 = add(10, 20); // 调用第一个 double r2 = add(10.5, 20.5); // 调用第二个 int r3 = add(10, 20, 30); // 调用第三个5.3 重载的规则
方法名必须相同
参数列表必须不同(个数不同、类型不同、类型顺序不同,满足其一即可)
与返回值类型无关
以下两种情况不构成重载:
// 错误:只有返回值不同,参数列表相同 public static int add(int x, int y) { ... } public static double add(int x, int y) { ... } // 编译报错 // 错误:只有形参名不同 public static int add(int x, int y) { ... } public static int add(int a, int b) { ... } // 编译报错5.4 方法签名
你可能好奇:既然方法名相同,编译器是怎么区分它们的?
答案在于方法签名。经过编译后,方法的名字不再是简单的"add",而是"方法全路径名 + 参数列表"的组合。比如:
add(int, int)→ 方法签名为add(II)Iadd(double, double)→ 方法签名为add(DD)D
这就是为什么参数列表不同的同名方法可以共存的原因——它们在底层有完全不同的标识。
六、递归:方法调用自身
6.1 递归的思想
从前有座山,山上有座庙,庙里有个老和尚给小和尚讲故事,讲的是:"从前有座山,山上有座庙……"
这个故事的特征是:自身中包含了自己。这种思想在编程中叫做递归。
递归的核心是:把一个大问题分解成与原问题解法相同的子问题,直到子问题简单到可以直接求解。
6.2 递归的两个必要条件
递归公式:将原问题拆分成子问题的规则
递归出口:递归结束的条件
以阶乘为例:
递归公式:
N! = N * (N-1)!递归出口:
1! = 1
6.3 代码实现:递归求阶乘
public static int factorial(int n) { if (n == 1) { return 1; // 递归出口 } return n * factorial(n - 1); // 递归公式 }6.4 递归的执行过程
以factorial(3)为例,执行过程如下:
factorial(3) 开始 → factorial(2) 开始 → factorial(1) 开始 → factorial(1) 结束,返回 1 → factorial(2) 结束,返回 2 × 1 = 2 → factorial(3) 结束,返回 3 × 2 = 6每次调用方法时,Java会在栈中分配一块空间(称为栈帧),记录当前调用的参数和返回位置。递归结束时,栈帧从顶到底依次弹出,最终得到结果。
6.5 递归练习
练习1:按顺序打印数字的每一位
public static void printDigits(int num) { if (num > 9) { printDigits(num / 10); } System.out.println(num % 10); } // 输入1234,输出1 2 3 4练习2:递归求1到N的和
public static int sum(int num) { if (num == 1) { return 1; } return num + sum(num - 1); }练习3:求斐波那契数列第N项
斐波那契数列的定义: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); }注意: 递归求斐波那契的效率很低,因为存在大量重复计算。比如计算fib(40)时,fib(3)会被重复计算上千万次。实际开发中,斐波那契更适合用循环来实现。
七、总结
今天我们学习了Java方法的全部核心知识:
方法的定义:修饰符 + 返回值类型 + 方法名 + 参数列表 + 方法体
方法的调用:定义时不执行,调用时才执行,可多次调用
形参和实参:实参的值拷贝给形参,两者是独立的实体
方法重载:同名不同参,编译器自动匹配
递归:方法调用自身,必须有递归公式和递归出口
方法让代码从"线性执行"升级为"模块化组织",是Java编程的重要里程碑。接下来,我们将马上进入Java的面向对象世界——类和对象,敬请期待!