Java6:Java方法详解:让代码告别重复,拥抱复用
目录写在前面一、方法是什么用生活场景理解二、方法的定义与使用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; }执行结果第一次调用之前 调用方法中x10 y20 第一次调用之后ret30 第二次调用之前 调用方法中x30 y50 第二次调用之后ret80可以看到同一个方法可以被多次调用每次传入不同的参数得到不同的结果。实战用方法简化阶乘求和回想一下我们在循环章节写的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; }运行结果交换前a10 b20 交换后a10 b20明明在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! 16.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的面向对象世界——类和对象敬请期待