前言:
对于代码来说,如果一直都是在main函数内写,那么像大型工程中几千几万行的代码,难道每次想找需要修改的bug都要一点一点审吗,所以我们有了方法这个概念。
每文一图:
让我们走进方法的学习ba!
方法的使用详解:- 一.方法的基本用法
- 1.什么是方法(method)
- 2.方法定义语法
- 3.方法调用的执行过程
- 4.实参和形参的关系(重要)
- 二.方法的重载
- 1.重载要解决的问题
- 2. 使用重载
- 三.方法递归
- 1.递归的概念
- 2.递归执行过程分析
- 3.递归经典题目
- 4.总结:
1.什么是方法(method)
首先我们来了解一下,什么是方法。方法就是一个代码片段,类似于 C 语言中的 “函数”。
对于方法的意义,我们可以总结,但重在体会:
- 是能够模块化的组织代码(当代码规模比较复杂的时候)。
- 做到代码被重复使用, 一份代码可以在多个位置使用。
- 让代码更好理解更简单。
- 直接调用现有方法开发, 不必重复造轮子。
在上一集中,我们说过一个代码,计算1-5的阶乘的和,如果我们按照以前的写法是这样子的:
public static void main(String[] args) {
//在main函数中写
int sum = 0;
for (int i = 1; i <= 5; i++) {
int tmp = 1;
for (int j = 1; j <= i; j++) {
tmp *= j;
}
sum += tmp;
}
System.out.println("sum = " + sum);
}
这里有两个循环,如果有时候不留神就会写错,而且阅读起来还比较不好理解,如果使用方法,每一个方法对应一部分运算,就可以更清晰明了了。我们来写一遍:
public static int fac(int n) {
//计算每一个数的阶乘
int ret = 1;
for (int i = 1; i <= n; i++) {
ret = ret *i;
}
return ret;
}
public static int facSum(int n) {
//计算阶乘合起来的和
int sum = 0;
for (int i = 1; i <= n; i++) {
sum = sum + fac(i);
}
return sum;
}
public static void main(String[] args) {
//输入n 输出阶乘和的结果
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
System.out.println(facSum(n));
}
是不是,我们将其分为三部分,当阶乘出错我们就去找每个数阶乘的那一部分,如果是和出错了就找和的那部分,然后更改就可以了。
这就是方法的优势。
2.方法定义语法
看见方法的优势了,那我们就开始学习方法到底是怎么样的语法吧。
基本语法:
// 方法定义
public static 方法返回值 方法名称([参数类型 形参 ...]){
方法体代码;
[return 返回值];
}
// 方法调用
返回值变量 = 方法名称(实参...);
这样看可能很枯燥看不懂,我们在代码中演示:
//用简单的加法来演示
//方法的定义
public static int sum(int a,int b){
//这里 int 就是方法返回值的类型,然后sum就是方法名称,后面括号的就是类型和形参
return a+b;
//如果有返回值的就要写return 和 返回值
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int a = scanner.nextInt();
int b = scanner.nextInt();
int ret = sum(a, b);
//方法调用 调用sum方法 传参a和b 接收返回值
System.out.println("ret = " + ret);
//方法调用,方法名sum
}
这就是方法!多写几次就熟练起来了,至于前面为什么是public static我们会在后面的博客中写道,这里就先统一在创建方法的时候就是默认前面。
当然对于方法的使用也有注意事项:
- public 和 static 两个关键字在此处具有特定含义,我们暂时不讨论, 后面会详细介绍。
- 方法定义时,参数可以没有,每个参数要指定类型。
- 方法定义时,返回值也可以没有,如果没有返回值,则返回值类型应写成 void。
- 方法定义时的参数称为 “形参”,方法调用时的参数称为 “实参”。
- 方法的定义必须在类之中,代码书写在调用位置的上方或者下方均可。
- Java 中没有 “函数声明” 这样的概念。
3.方法调用的执行过程
对于方法我们已经知道怎么创建和使用了,但是它的性质,或者说它用起来对于执行起来是怎么样的,我们也需要来理解一下:
基本规则:
1.定义方法的时候,不会执行方法的代码,只有调用的时候才会执行。
比如:
public static int sum(int a,int b){
return a+b;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int a = scanner.nextInt();
int b = scanner.nextInt();
System.out.println("hello");
//无调用 不执行 打印结果 hello
}
这就展现了方法其中的一个优点,不使用的时候不会调用,我们需要的时候就调用,很方便,我们可以写好一个功能比如计算两数之和,想要就调用,不用就放那,用的时候只需要再调用一下。
2.当方法被调用的时候, 会将实参赋值给形参。
方法之所以可以调用起来的时候和main方法或者其他方法建立起有机关联,就是因为方法被调用的时候,可以将实参赋值给形参,就相当于拿到了你的试卷帮你写。
如:
public static int sum(int A,int B){
return A+B;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int a = scanner.nextInt();
int b = scanner.nextInt();
System.out.println(sum(a,b));
//a b 实参传给sum方法的A B
}
3.参数传递完毕后,就会执行到方法体代码。一个方法可以被多次调用。
在调用了该方法,又进行完传参之后,就会执行到方法体代码。
如:
public static int sum(int A,int B){
System.out.println("hello");
//会打印hello 因为执行了方法体内代码
return A+B;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int a = scanner.nextInt();
int b = scanner.nextInt();
System.out.println(sum(a,b));
System.out.println(sum(a,b));//调用俩次输出俩次
//a b 实参传给sum方法的A B
}
4.当方法执行完毕之后(遇到 return 语句),就执行完毕,回到方法调用位置继续往下执行。
如果是有返回值的方法,当遇到return 语句就是执行完毕了,如果是void类型的方法,就是把方法体的代码执行下去就结束。然后再回到方法调用的地方去继续执行。
这就是方法调用的执行过程。
最后我们再来举个例子:
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;
}
// 执行结果
// 一次调用方法之前
// 调用方法中 x = 10 y = 20
// 第一次调用方法之后
// ret = 30
// 第二次调用方法之前
// 调用方法中 x = 30 y = 50
// 第二次调用方法之后
// ret = 80
4.实参和形参的关系(重要)
我们再来看一次这个代码:
//代码示例: 交换两个整型变量
public static void swap(int x,int y) {
int tmp = x;
x = y;
y = tmp;
}
public static void main(String[] args) {
int a = 10;
int b = 20;
swap(a, b);
System.out.println("a = " + a + " b = " + b);
//打印结果 a=10 b=20
}
这里我们不是调用了方法吗,我们不是将值传过去调用了吗,实际上,这可能并没有你想象的那么简单:
刚才的代码,没有完成数据的交换。对于基础类型来说,形参相当于实参的拷贝。即传值调用。
int a = 10; int b = 20; int x = a; int y = b; int tmp = x; x = y; y = tmp;
实际上,他x和y交换,关我a和b什么事情?这就是传值调用,实际上新建了两个量在操作。
对于它在传值调用,我们的解决办法也是用的,你传你的值过去没用,那我就传存放你的地方过去,直接把你改了(传引用类型参数):
//对于数组就是传的是他的地址(这里只做解释 具体数组可到数组篇去看)
public static void main(String[] args) {
int[] arr = {10, 20};
swap(arr);
System.out.println("a = " + arr[0] + " b = " + arr[1]);
}
public static void swap(int[] arr) {
int tmp = arr[0];
arr[0] = arr[1];
arr[1] = tmp;
}
// 运行结果
// a = 20 b = 10
二.方法的重载
重载,第一次听这个词想到的是什么,重新下载?重新加载?这里可不是这些意思。对于方法的重载,是有些时候我们需要用一个函数同时兼容多种参数的情况,我们就可以使用到方法重载。
1.重载要解决的问题对于重载,是帮我们解决用一个函数同时兼容多种参数的情况,也就是比如有时候我们使用的方法传参过去的是两个int,有时候是两个double,那么需要定义几个不同类型但作用相同的方法吗,这就用到我们重载了。
//这就是问题所在
public static int add(int x, int y) {
return x + y;
}
public static void main(String[] args) {
int a = 10;
int b = 20;
int ret = add(a, b);
System.out.println("ret = " + ret);
double a2 = 10.5;
double b2 = 20.5;
double ret2 = add(a, b);//不同类型 err
System.out.println("ret2 = " + ret2);
//由于参数类型不匹配, 所以不能直接使用现有的 add 方法
}
所以我们就到重载的part了,重载就是同一个方法,但参数传的是不一样的,系统会自动选择调用到哪个参数的方法处。
就像这样:
public static void main(String[] args) {
int a = 10;
int b = 20;
int ret = add(a, b);
System.out.println("ret = " + ret);
double a2 = 10.5;
double b2 = 20.5;
double ret2 = add(a2, b2);
System.out.println("ret2 = " + ret2);
}
public static int add(int x, int y) {
return x + y;
}
public static double add(double x, double y) {
return x + y;
}
2. 使用重载
既然我们知道了这个知识,那么我们的方法就可以适用于多个类型了。
比如:
public static void main(String[] args) {
int a = 10;
int b = 20;
int ret = add(a, b);
System.out.println("ret = " + ret);
double a2 = 10.5;
double b2 = 20.5;
double ret2 = add(a2, b2);
System.out.println("ret2 = " + ret2);
double a3 = 10.5;
double b3 = 10.5;
double c3 = 20.5;
double ret3 = add(a3, b3, c3);
System.out.println("ret3 = " + ret3);
}
public static int add(int x, int y) {
//int类型变量
return x + y;
}
public static double add(double x, double y) {
//double类型变量
return x + y;
}
public static double add(double x, double y, double z) {
//不仅是变量,传参的数的数量也可以变
return x + y + z;
}
在这里,方法的名字都叫 add。但是有的 add 是计算 int 相加,有的是 double 相加;有的计算两个数字相加,有的是计算三个数字相加。同一个方法名字, 提供不同版本的实现, 称为方法重载。
那么用作用就有规则,我们来看看:重载的规则。
针对同一个类:
1.方法名相同 。
2.方法的参数不同(参数个数或者参数类型)。
2.方法的返回值类型不影响重载。
也就是说,在同一个类上,方法的重载是看方法名+其传参的,方法名系统传参不同就构成重载,无论返回值的类型是什么。
我们来看一下例子:
//错误示范
//同方法名同传参类型 不同返回类型不构成重载
public static void main(String[] args) {
int a = 10;
int b = 20;
int ret = add(a, b);
System.out.println("ret = " + ret);
}
public static int add(int x, int y) {
return x + y;
}
public static double add(int x, int y) {
return x + y;
}
}
结论:
当两个方法的名字相同,参数也相同,但是返回值不同的时候,不构成重载。当两个方法的名字相同,参数不相同,不管返回值,都是重载。
三.方法递归
1.递归的概念
一个方法在执行过程中调用自身, 就称为 “递归”。递归相当于数学上的 “数学归纳法”,有一个起始条件,然后有一个递推公式。
例如, 我们求 N!
起始条件: N = 1 的时候, N! 为 1. 这个起始条件相当于递归的结束条件.
递归公式: 求 N! , 直接不好求, 可以把问题转换成 N! => N * (N-1)!
实际上递归就是实现一件事情的过程有许多相同的地方,所以我们可以写一个归纳好的逻辑,然后使用,并在其中自己调用自己。
多说无益,我们直接上代码:
题目:求 N 的阶乘。
按往常,我们可能会写一个for循环,但是我们不是说过有时候可能需要重复使用,所以可以把他写成一个方法。而这个方法,还是递归的。
public static void main(String[] args) {
int n = 5;
int ret = fac(n);
System.out.println("ret = " + ret);
}
public static int fac(int n) {
if (n == 1) {
return 1;
}
return n * fac(n - 1); // fac调用函数自身
}
// 执行结果
// ret = 120
如上面的代码,我们想实现的是求n的阶乘,也就是求n*(n-1)* ...,所以我们可以写出一个递归,每一次递归回去就乘上n-1。
2.递归执行过程分析什么?对于上面的东西没看懂,其实递归确实是一种抽象的东西,所以我们可以对其进行一定的分析,让我们更好的吸收。
首先,要想理解清楚递归,必须先理解清楚 “方法的执行过程”,尤其是 "方法执行结束之后,回到调用位置继续往下执行。
我们还是以n的阶乘为例,但是我们配上图解,不能理解就画图解!
public static void main(String[] args) {
int n = 5;
int ret = fac(n);
System.out.println("ret = " + ret);
}
public static int fac(int n) {
if (n == 1) {
return 1;
}
return n * fac(n - 1); // fac调用函数自身
}
// 执行结果
// ret = 120
那么这样调用的时候,内存是什么情况呢?
关于 “调用栈”:
方法调用的时候,会有一个 “栈” 这样的内存空间描述当前的调用关系。称为调用栈。每一次的方法调用就称为一个 “栈帧”,每个栈帧中包含了这次调用的参数是哪些,返回到哪里继续执行等信息。
这就是递归。
3.递归经典题目
1.按顺序打印一个数字的每一位(例如 1234 打印出 1 2 3 4)
参考代码:
public static void print(int num) {
if (num > 9) {
print(num / 10);
}
System.out.println(num % 10);
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
print(n);
}
2.递归求 1 + 2 + 3 + … + 10
参考代码:
public static int sum(int num) {
if (num == 1) {
return 1;
}
return num + sum(num - 1);
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
sum(n);
}
3.求斐波那契数列的第 N 项
斐波那契数列(Fibonacci sequence),又称黄金分割数列,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34。这个数列从第3项开始,每一项都等于前两项之和。
参考代码:
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();
fib(n);
}
这里需要注意,当我们求的是40左右的斐波那契数的时候,其实编译器需要运行的时间已经开始变得慢起来了,这是因为递归需要进行了大量的重复运算,所以导致运行过慢。
这里更好的选择是循环(迭代)去解决这个斐波那契数列的问题。
//迭代版斐波那契
public static int fib(int n) {
int last2 = 1;
int last1 = 1;
int cur = 0;
for (int i = 3; i <= n; i++) {
cur = last1 + last2;
last2 = last1;
last1 = cur;
}
return cur;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
fib(n);
}
4.总结:
递归是一种重要的编程解决问题的方式。
有些问题天然就是使用递归方式定义的(例如斐波那契数列, 二叉树等),此时使用递归来解就很容易.
有些问题使用递归和使用非递归(循环)都可以解决。那么此时更推荐使用循环,相比于递归,非递归程序更加高效。
具体要看要求。
这就是本篇方法的使用详解的全部内容啦,如果觉得还不错或者感觉对你有帮助,不妨点赞关注一键三连,一起学习,共同努力!也可以期待这个系列接下来的博客噢。
链接:都在这里! Java SE 带你从零到一系列
还有一件事:



