目录
一、方法的基本用法
🚆 什么是方法
- 方法就是一个代码片段,Java中的方法就是其他编程语言中的函数。都是将实现某一个功能的代码封装。
- 方法包括:方法名、参数、返回值、方法体
⭐ 方法存在的意义
- 能够模块化的组织代码(当代码规模比较复杂的时候)。
- 做到代码被重复使用, 一份代码可以在多个位置使用。
- 让代码更好理解更简单。
- 直接调用现有的方法, 不必重复造轮子。
🍉 定义方法
🍓基本语法
// 方法定义
public static 方法返回值 方法名称([参数类型 形参 ...]){
方法体代码;
[return 返回值];
}
// 方法调用
返回值变量 = 方法名称(实参...);
🌊代码示例:
public class TestDemo {
/**
* Add函数 :计算两个整数的和
* @param x:形参 x ->接收main方法中传的10
* @param y:形参 y ->接收main方法中传的25
* @return :两个整数相加后的值 35
*/
public static int Add(int x, int y) {
int sum = x + y;
return sum;
}
//main方法(函数)
public static void main(String[] args) {
int ret = Add(10, 25); //方法调用
System.out.println(ret);
//System.out.println(Add(10,25)); //函数的返回值,支持链式调用
}
}
💥注意事项
- public 和 static 两个关键字在此处具有特定含义, 后面会详细介绍。
- 方法定义时, 参数可以没有. 每个参数要指定类型。
- 方法定义时, 返回值也可以没有, 如果没有返回值, 则返回值类型应写成 void。
- 方法定义时的参数称为 "形参", 方法调用时的参数称为 "实参"。
- 方法的定义必须在类之中, 代码书写在调用位置的上方或者下方均可。
- Java 中没有 "函数声明" 这样的概念。
- 如果方法没有返回值,则不可以使用链式调用。
🍉方法调用的执行过程
🍓基本规则
- 定义方法的时候不会执行方法的代码,只有调用的时候才会执行。
- 当方法被调用的时候会将实参赋值给形参。
- 参数传递完毕后就会执行到方法体代码。
- 当方法执行完毕之后(遇到 return 语句), 就会回到调用的方法位置继续往下执行。
- 一个方法可以被多次调用。
🌊代码示例:
public class TestDemo {
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println("第一次调用方法之前");
int ret = add(a, b);
System.out.println("第一次调用方法之后");
System.out.println("ret = " + ret);
System.out.println("第二次调用方法之前");
ret = add(30, 50);
System.out.println("第二次调用方法之后");
System.out.println("ret = " + ret);
}
public static int add(int x, int y) {
System.out.println("调用方法中 x = " + x + " y = " + y);
return x + y;
}
}
运行结果
🌊代码示例:求某个数的阶乘之和
public class TestDemo {
/**
* 求某一个数的阶乘
*
* @param n 传的数字
* @return 5! = 5*4*3*2*1
*/
public static int fac(int n) {
int ret = 1;
for (int i = 1; i <= n; i++) {
ret *= i;
}
return ret;
}
/**
* 求一个数的阶乘之和
*
* @param n 传的数字
* @return 5! = 1! + 2! + 3! + 4! + 5!
*/
public static int facSum(int n) {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += fac(i);
}
return sum;
}
public static void main(String[] args) {
int retsum = facSum(5);
System.out.println(retsum);
}
}
🍉 实参和形参的关系
🌊代码示例:交换两个整型变量
public class TestDemo {
public static void Swap(int x, int y) {
int temp = x;
x = y;
y = temp;
}
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);
}
}
运行结果
🍓原因分析:
- 代码没有完成数据的交换。对于基础类型来说, 形参相当于实参的拷贝. 即传值调用。
- 对 x 和 y 的修改, 不影响 a 和 b。
- 局部变量都是在栈上开辟空间,Java中是拿不到栈上的地址。
🌌 解决办法:使用数组。因为数组是后面所介绍的内容,所以等到介绍完数组再给出答案。
🍉 没有返回值的方法
✨方法的返回值是可选的,有时候是可以没有的。这个文章前面也说的了,这里举个栗子。
🌊代码示例:在方法中输出两个变量。
public class TestDemo {
public static void main(String[] args) {
int a = 10;
int b = 20;
print(a, b);
}
public static void print(int x, int y) {
System.out.println("x = " + x + " y = " + y);
}
}
⚡前面所写的交换两个变量的方法就是没有返回值的,返回类型是 void。
二、方法的重载
✨有些时候我们需要用一个函数同时兼容多种参数的情况, 我们就可以使用到方法重载。
🍉重载需要解决的问题
public class TestDemo {
public static int add(int x, int y) {
return x + y;
}
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println(add(a, b));
double c = 10.5;
double d = 20.6;
System.out.println(add(c, d));
}
}
运行结果
已经有一个add方法计算两个整数的和,如果想计算两个浮点数的和,直接传参就会导致参数类型不匹配, 因此不能直接使用现有的 add 方法。如果想再计算两个浮点数的和就需要再写一个方法。
public class TestDemo {
//计算两个整数的和
public static int addInt(int x, int y) {
return x + y;
}
//计算两个浮点数的和
public static double addDouble(double x, double y) {
return x + y;
}
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println(addInt(a, b));
double c = 10.5;
double d = 20.6;
System.out.println(addDouble(c, d));
}
}
运行结果
✨这样的写法肯定是没问题的,但是在Java中可以使用同一个方法名。
🍉使用方法重载
public class TestDemo {
//计算两个整数的和
public static int add(int x, int y) {
return x + y;
}
//计算三个整数的和
public static int add(int x, int y, int z) {
return x + y + z;
}
//计算两个浮点数的和
public static double add(double x, double y) {
return x + y;
}
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println(add(a, b));
System.out.println(add(10, 20, 30));
double c = 10.5;
double d = 20.6;
System.out.println(add(c, d));
}
}
运行结果
- 三个方法的名字都叫 add,但是有的 add 是计算 int 相加;有的是 double 相加;有的计算两个数字相加;有的是计算三个数字相加。
✨同一个方法名字, 提供不同功能的实现, 称为方法重载。
🍉重载的规则
- 方法名相同。
- 方法的参数不同(参数个数或者参数类型)。
- 方法的返回值类型不影响重载(返回值一样或者不一样都和重载无关)。
- 重载识别的时候只识别方法名和参数,不会识别方法的返回值类型。
🍓在Java语言规范中指出:
- 如果一个类的两个方法(无论是在同一个类中声明的,还是由一个类继承的,或者一个声明的和一个继承的)具有相同的名称但签名不是重载等价的,那么该方法名被称为重载。 (重载不是针对同一类的)。
三、方法递归
🍉递归的概念
🚀一个方法在执行过程中调用自身, 就称为 "递归".
⭐递归相当于数学上的 "数学归纳法", 有一个起始条件, 然后有一个递推公式.
例如求 N!
- 起始条件: N = 1 的时候, N! 为 1. 这个起始条件相当于递归的结束条件。
- 递归公式: 求 N! , 直接不好求, 可以把问题转换成 N! => N * (N-1)!
🌊代码示例:求N的阶乘
public class TestDemo2 {
public static int fun(int x) {
if (x == 1) {
return 1;
}
return x * fun(x - 1);
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
System.out.println(fun(n));
}
}
- 1的阶乘为1,所如果n值为1直接返回1;2! = 2 * 1! ,5!= 5 * 4!,所以返回 x * fun(x-1)。
🍉递归执行过程分析
🍓递归程序的执行过程不太容易理解, 要想理解清楚递归, 必须先理解清楚 "方法的执行过程", 尤其是 "方法执行结束之后, 回到调用位置继续往下执行"
public class TestDemo2 {
public static int factor(int n) {
System.out.println("函数开始, n = " + n);
if (n == 1) {
System.out.println("函数结束, n = 1 ret = 1");
return 1;
}
int ret = n * factor(n - 1);
System.out.println("函数结束, n = " + n + " ret = " + ret);
return ret;
}
public static void main(String[] args) {
int n = 5;
int ret = factor(n);
System.out.println("ret = " + ret);
}
}
运行结果
🌀执行过程图
🍓关于 "调用栈"
- 方法调用的时候, 会有一个 "栈" 这样的内存空间描述当前的调用关系,称为调用栈。
- 每一次的方法调用就称为一个 "栈帧",每个栈帧中包含了这次调用的参数是哪些,返回到哪里继续执行等信息。
🍉递归练习
🎄 按照顺序打印一个整数的每一位
//代码1:
public class TestDemo2 {
public static void everOne(int n) {
if (n > 9) {
everOne(n / 10);
}
System.out.print(n % 10 + " ");
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
everOne(n);
}
}
//代码2
public class TestDemo2 {
public static void everOne(int n) {
if (n < 10) {
System.out.print(n % 10 + " ");
} else {
everOne(n / 10);
System.out.print(n % 10 + " ");
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
everOne(n);
}
}
🎄 写一个递归方法,输入一个非负整数,返回组成它的数字之和.
public class TestDemo2 {
public static int everOne(int n) {
if (n < 10) {
return n;
}
return everOne(n / 10) + n % 10;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
System.out.println(everOne(n));
}
}
🎄 斐波那契数列
public class TestDemo2 {
public static int fib(int n) {
if (n == 1 || n == 2) {
return 1;
}
return fib(n - 1) + fib(n - 2);
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
for (int i = 1; i <= n; i++) {
int ret = fib(i);
System.out.println(ret);
}
}
}
✨使用递归求斐波那契数列并不是一个好的方式,重复计算的步骤太多,降低了代码的效率。
🚀可以使用循环的方式来求斐波那契数列问题, 避免出现冗余运算。
public class TestDemo2 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int f1 = 1;
int f2 = 1;
int f3 = 0;
System.out.println(f1);
System.out.println(f2);
for (int i = 3; i <= n; i++) {
f3 = f1 + f2;
f1 = f2;
f2 = f3;
System.out.println(f3);
}
}
}
🎄青蛙跳台阶
- 一只青蛙一次可以跳上 1 级台阶,也可以跳上2 级。求该青蛙跳上一个n 级的台阶总共有多少种跳法。(变相的斐波那契)
- 当青蛙选择第一次跳一个台阶时,跳完还剩下 n-1 个台阶;当青蛙选择第一次跳两个台阶时,跳完还剩下 n-2 个台阶。所以可以推出青蛙跳 n 个台阶的跳法总数: f(n) = f(n-1)+f(n-2)。
//代码1
public class TestDemo2 {
public static int fib(int n) {
if (n == 1 || n == 2) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
for (int i = 1; i <= n; i++) {
int ret = fib(i);
System.out.println(ret);
}
}
}
//代码2
public class TestDemo2 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int f1 = 1;
int f2 = 2;
int f3 = 0;
System.out.println(f1);
System.out.println(f2);
for (int i = 3; i <= n; i++) {
f3 = f1 + f2;
f1 = f2;
f2 = f3;
System.out.println(f3);
}
}
}
🎄使用递归求解汉诺塔问题
- 汉诺塔问题是一个经典的问题。汉诺塔(Hanoi Tower),又称河内塔,源于印度一个古老传说。
- 大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。
- 大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。
- 并且规定,任何时候,在小圆盘上都不能放大圆盘,且在三根柱子之间一次只能移动一个圆盘。
🚀解题思路:
- 假设三根柱子命名为:A、B、C
✨ 移动规则:
- 如果A柱上有一个盘子:A -> C 移动 1 次
- 如果A柱上有两个盘子:A -> B A -> C B -> C 移动 3 次
- 如果A柱上有三个盘子:A -> C、A -> B、C -> B、A -> C、B -> A、B->C、A -> C 移动 7 次
🍓规律:如果有N个盘子:移动 2*n -1 次
- 如果有N个盘子,先将A柱上的 N-1 个盘子借助C柱移动到B柱上,再将A柱中剩下的一个盘子(最底下的那个盘子)移动到C柱上,最后将B柱上的 N-1个盘子借助A柱移动到C柱上。
import java.util.Scanner;
public class TestDemo {
public static void move(char p1,char p2){
System.out.print(p1 + " -> " + p2+" ");
}
/**
* @param n 表示盘子个数
* @param pos1 盘子所在的起始位置
* @param pos2 盘子需要中转的位置
* @param pos3 盘子的结束位置
* @return 移动的顺序
*/
public static void hanio (int n,char pos1,char pos2,char pos3){
if(n==1){
//如果只有一个盘子:A -> C
move(pos1,pos3);
}else{
// 将n-1 个盘子借助C柱移动到B柱上
hanio(n-1,pos1,pos3,pos2);
//将A柱上剩下的一个盘子移动到C柱上
move(pos1,pos3);
// 将B柱中的盘子(n-1个盘子)借助A柱移动到C柱上
hanio(n-1,pos2,pos1,pos3);
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
for (int i = 1; i <=n ; i++) {
hanio(i,'A','B','C');
System.out.println();
}
}
}
🍉 递归小结
- 递归是一种重要的编程解决问题的方式。
- 有些问题天然就是使用递归方式定义的(例如斐波那契数列, 二叉树等), 此时使用递归来解就很容易。
- 有些问题使用递归和使用非递归(循环)都可以解决。那么此时更推荐使用循环, 相比于递归, 非递归程序更加高效。