Java方法 - 方法重载 - 递归
Posted Ju_19t.W-cheri.G
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java方法 - 方法重载 - 递归相关的知识,希望对你有一定的参考价值。
Java方法
✔️前言
哈喽大家好,我是Aaron,本文带来在Java中方法的使用以及方法涉及知识点的分享,觉得对各位有帮助的可以给出三连支持哦~
👍点赞👍 + 👀关注👀 + 🤏收藏🤏
正文开始
一,方法的基本用法
对于初学者来说,方法的概念是第一次听说,但其实他就类似于C语言中的函数,是一个可以多次使用,并且完成单一功能的代码块。
那么,为什么要引入方法的概念呢?
我们可以想一个问题,如果没有方法的概念,那就意味着一个工程所有的功能实现代码全部要写在main函数里,那将会使代码变得非常不具可阅读性,并且难以维护,如果盲目修改代码中的某一处,很有可能使程序多出无数个bug,所以,引入方法的概念是必不可少的~
如果使用方法去完成每一个单独的功能,则将便于我们对程序进行维护,如果某一个功能出现bug,只需要在完成其功能的方法进行修改,而不会影响到其他方法和main方法的实现逻辑。
举一个例子演示使用方法进行编程的好处:
例如,编程实现1!~ 5!求和。
如果用普通方法实现,需要使用到循环嵌套:
public class Method {
/**
* 求1! ~ 5!的求和
* @param args
*/
public static void main(String[] args) {
int sum = 0;
for (int i = 1; i <= 5; i++) {
int ret = 1;
for (int j = 1; j <= i; j++) {
ret *= j;
}
sum += ret;
}
System.out.println(sum);
}
}
使用嵌套循环比较容易写出bug,而利用方法就可以有效避免,并且能将功能单一化:
public class Method {
/**
* 求n的阶乘
* @param args
*/
public static int fac(int n) {
int ret = 1;
for (int i = 1; i <= n; i++) {
ret *= i;
}
return ret;
}
/**
* 求1~5的阶乘之和
* @param args
*/
public static int sumFac(int n) {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += fac(i);
}
return sum;
}
public static void main(String[] args) {
int n = 5;
int ret = sumFac(n);
System.out.println(ret);
}
}
使用两个不同的方法完成两个独立的功能,这种写法更加体现代码的可读性和可维护性~
方法的定义规则
在Java中的使用规则需要用到类和对象的概念,但那不是本文主要讲述的内容,所以这里不多赘述,只需要知道初学者大部分使用的方法都有固定的写法:
例如:
public class Method {
public static void func() {
System.out.println("hehe");
}
public static void main(String[] args) {
func();
}
}
这里的func方法采用的写法就是
public + static + 返回类型 + 方法名(参数列表)
我们只需要知道他固定的写法格式即可,具体含义在学习了类和对象即可理解,有兴趣的小伙伴也可以看博主的专栏JavaSE
里面记录了JavaSE需要掌握的全部内容~
注意:
- 参数列表中必须给出参数的数据类型。
(可以没有参数) - 方法名必须采用小驼峰写法。
总结:
- public和static在这里有特殊的含义,但不在本文作详细介绍,有兴趣可以看博主专栏JavaSE。
- 在方法定义中,可以没有参数,但如果有参数,一定要指明参数类型。
- 方法定义中,可以没有返回值,但方法定义的返回类型应该是void类型。
- 方法调用时的参数称为实参,方法定义时的参数称为形参。
- 方法的定义必须在类当中,但在类当中的具体为止不受约束,可处于代码上下文任意位置。
- Java中没有函数声明的概念。
方法的执行过程
这里我们需要注意一点,与C语言不同的是,Java中没有函数声明之类的东西,也就是说,我们在写Java代码时,不需要对方法进行声明,这也就意味着,方法的所处的位置是任意的。
我们回忆一下在学习C语言时,函数定义必须在main函数定义之前,否则就需要在使用该函数之前进行函数声明,而Java中因为没有方法声明这种概念,所以也就不需要将方法定义在main函数之前。
下面用一段代码的执行过程分析方法的执行流程:
public class Method {
/**
* 求n的阶乘
* @param n
* @return
*/
public static int fac(int n) {
int ret = 1;
for (int i = 1; i <= n; i++) {
ret *= i;
}
return ret;
}
public static void main(String[] args) {
int ret = fac(5);
System.out.println(ret);
}
}
运行结果:
我们这里分析一下代码运行流程:
首先程序一定是从上往下执行,但是,遇到方法时并不执行,而是当方法调用时才开始执行。
任何程序都是以main函数为起点开始执行的,进入main函数之后执行代码:int ret = fac(5);
这行代码的意思是将返回类型为int的方法fac的返回值赋值给ret,从这里开始进入fac方法。
注意: main函数中fac方法是方法调用,括号里的是实参,方法定义中参数列表里的是形参,实参和形参的个数必须一致,数据类型必须一一对应。
方法调用基本规则:
- 方法代码定义时,不会被执行,只有被调用后才会被执行,也就意味着同一个方法可能被执行多次。
- 方法在调用时,会将实参临时拷贝给形参。(具体只是可以看图解函数栈帧 - 函数的创建与销毁)。
- 参数传递(拷贝)后,就会开始执行方法的代码。
- 当方法代码被执行完之后(遇到return)就会返回被调用方法中,继续执行下面的代码。
- 一个方法可以被多次调用。
Java理解方法(函数)栈帧
函数栈帧是用来理解C语言的重要只是,有兴趣的小伙伴可以阅读我的博文:图解函数栈帧 -
函数的创建与销毁
博主在里面用C语言和汇编语言详细介绍了函数的底层实现。
Java是面向对象的语言,其反汇编的封装做的比较完善,所以不容易分析其方法栈帧的原理,所以这里博主通过画图的方式简单介绍即可。
- 我们知道,任何程序的开端都是main方法,而所有的方法都是在栈上以压栈的形式开辟的。
- 在main方法里调用func方法,则会在栈顶为func方法开辟栈帧空间。
- 当func方法return之后,则销毁func方法的栈帧,并带回返回值交给main方法中调用方法的执行语句。
实参和形参的关系
实参和形参的关系是我们必须了解的内容,形参其实就是实参的一份临时拷贝,即实参可以影响形参,但形参无法影响实参。
举个简单的例子:交换两个整数
public class Method {
public static void swap(int x, int y) {
int tmp = x;
x = y;
y = tmp;
}
public static void main(String[] args) {
int a = 3;
int b = 5;
System.out.println(a);
System.out.println(b);
swap(a, b);
System.out.println(a);
System.out.println(b);
}
}
运行结果:
可以发现,这里无法通过形参的交换而改变实参。
画图解释:
- 这里有两个实参。
- 将实参做一份临时拷贝给形参
- 通过第三个变量交换形参。
- 交换后
交换后形参发生了改变,但实参并没有进行交换,当该swap方法执行完之后,其方法栈帧将会被销毁,也就意味着这个方法什么功能都没能实现。
到这,可能有码友会问,那能不能类似于C语言,进行传址调用呢?
答案是否定的,在Java中没有取地址(&)这种操作,也没有指针的概念,只有类似的引用,所以想要完成这样的交换功能,只能通过引用的方式,但如果要介绍这道题的正确解法,需要用到类和对象以及引用的知识,就不多介绍,有需要的可以去JavaSE专栏找到相应文章。
这里只把相应代码展示,以供参考:
public class Method {
public static void swap(int[] arr) {
int tmp = arr[0];
arr[0] = arr[1];
arr[1] = tmp;
}
public static void main(String[] args) {
int[] arr = {10, 20};
swap(arr);
System.out.println("a = " + arr[0] + " b = " + arr[1]);
}
}
二,方法的重载
在Java中方法是可以重载的(overload)。
使用重载的目的
用一个简单的例子说明为什么需要重载。
比如:我要写一个加法方法。
public class Method {
public static int add(int x, int y) {
return x + y;
}
public static void main(String[] args) {
int a = 3;
int b = 5;
int ret = add(a, b);
System.out.println(ret);
}
}
这个方法只适用于两个整型的相加,如果我想让三个整型相加或者两个浮点数相加则做不到,必须重新写一个新的方法,其方法名不能和该方法相同。
而方法重载的概念就是改变参数列表,方法名不变,看以下代码:
public class Method {
public static int add(int x, int y, int z) {
return x + y + z;
}
public static int add(int x, int y) {
return x + y;
}
public static void main(String[] args) {
int a = 3;
int b = 5;
int c = 1;
int ret1 = add(a, b);
int ret2 = add(a, b, c);
System.out.println(ret2);
}
}
此时的public static int add(int x, int y, int z)和public static int add(int x, int y)就构成了重载的关系。
无独有偶。
public class Method {
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;
}
public static int add(int x, int y) {
return x + y;
}
public static void main(String[] args) {
int a = 3;
int b = 5;
int c = 1;
int ret1 = add(a, b);
int ret2 = add(a, b, c);
// System.out.println(ret2);
double d1 = 3.4;
double d2 = 2.4;
double ret3 = add(d1, d2);
System.out.println(ret3);
}
}
此时的public static double add(double x, double y)和public static int add(int x, int y, int z)以及public static int add(int x, int y)都构成了重载的关系。
这样写代码就可以共用一个函数名,通过不同的参数列表对不同的数据类型实现相同的功能。
重载的规则
- 方法名必须相同。
- 参数列表必须不同(个数,类型)。
- 与返回类型无关。
首先,方法名必须相同,因为实现的是相同的功能,我们可以理解为是同一个方法对于不同数据的衍生处理。
例如:
public class Method {
public static int add1(int x, int y) {
return x + y;
}
public static int add2(int x, int y) {
return x + y;
}
public static void main(String[] args) {
int a = 3;
int b = 5;
int ret1 = add1(a, b);
int ret2 = add2(a, b);
System.out.println(ret1);
System.out.println(ret2);
}
}
此时的add1和add2是完全不同的两个方法,他们不构成重载关系。
满足重载关系的条件还有参数列表的不同,也就是说参数个数,类型,有一个满足不同即可。
最后一点,也是初学者最容易产生误区的一点,方法的返回类型并不能影响重载。
也就是说如果方法名相同,参数列表不同的两个方法,不管返回类型是否一样,都构成重载关系。
而如果方法名相同,参数列表也相同的两个方法,不管返回类型是否一样,都不构成重载关系。
三,递归
程序调用自身的编程技巧称为递归( recursion)。递归作为一种算法在程序设计语言中被广泛应用。
一个过程或方法在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
以上概念来自百度百科,虽然话术比较官方,但还算言简意赅。
其实简单来说,程序运行时,某一个方法自己调用自己,就被称作递归。
其实递归很好理解,他就类似于我们高中学的"数学归纳法",或者又类似于"通项",我们就是要找出其起始条件,推出递归公式即可完成任务。
递归公式解决问题
例如:我们要求n!
其递归公式为n * (n - 1)!
我们要直接求n!不容易,但通过递归公式就很容易得到。
public class Method {
public static int fac(int n) {
if (n > 1) {
return n * fac(n - 1);
}else {
return 1;
}
}
public static void main(String[] args) {
int ret = fac(5);
System.out.println(ret);
}
}
运行结果:
又比如要求斐波那契数列,我们也可以直接使用递归公式。
public class Method {
public static int fib(int n) {
if (n == 1 || n == 2) {
return 1;
}else {
return fib(n - 1) + fib(n - 2);
}
}
public static void main(String[] args) {
int ret = fib(10);
System.out.println(ret);
}
}
运行结果:
递归分析过程
其实递归的名字是有由来的,递归表示的是两个动作,即递和归,当满足约束条件时,则递,不满足时,则归。
代码递归路线图
递归的过程其实是横向的,接下来画图分析。
就拿阶乘举例:
假设给定n为3。
-
下面展示递过程
-
接下来是归过程
此时就完成了递归的全过程,最终将3!的答案6返回给调用方法。
为了方便理解,再用另一种方法描述。
形象分析图
同样还是用阶乘的代码来描述。
如图:
假设这是一个阶乘方法的流程,从上至下运行。
遇到递归调用,则重新开辟栈帧然后进入新的栈帧。
最后一个fac方法走完之后(遇到return)往回归。
这样就完成了递归的内容,并将返回值带回。
说了那么多概念和解释,用几道简单的小例题加以巩固。
递归练习
- 示例代码1: 按顺序打印一个数字的每一位(例如 1234 打印出 1 2 3 4)
public class Method {
/**
* 按顺序打印一个数字的每一位(例如 1234 打印出 1 2 3 4)
* @param args
*/
public static void print(int n) {
if (n < 10) {
System.out.print(n + " ");
}else {
print(n / 10);
System.out.print(n % 10 + " ");
}
}
public static void main(String[] args) {
print(1234);
}
}
- 代码示例2: 递归求 1 + 2 + 3 + … + 10
public class Method {
/**
* 递归求 1 + 2 + 3 + ... + 10
* @param args
*/
public static int sum(int n) {
if (n > 1) {
return n + sum(n - 1);
}else {
return 1;
}
}
public static void main(String[] args) {
int ret = sum(10);
System.out.println(ret);
}
}
- 代码示例3: 写一个递归方法,输入一个非负整数,返回组成它的数字之和。 例如,输入1729,则应该返回1+7+2+9,它的和是19。
public class Method {
/**
* 写一个递归方法,输入一个非负整数,返回组成它的数字之和。 例如,输入 1729, 则应该返回1+7+2+9,
* 它的和是19
* @param args
*/
public static int sumEveryOne(int n) {
if (n < 10) {
return n;
}else {
return n % 10 + sumEveryOne(n / 10Java 基础语法方法的使用