1. 方法的基本用法
1.1 什么是方法
C语言中的函数,在Java中叫做"方法"
方法的作用和意义(同C):
- 让某一功能模块化,使得这个模块能够被复用,提高开发的效率
- 提高代码的可读性(某个方法有具体的名称)
例子:如果一道题让我们求1~100的和,我们可以直接在主函数中写出来:
int sum = 0; for (int i = 1; i <= 100 ; i++) { sum += i; } System.out.println(sum);
结果:5050
但如果要求我们写1~n的和呢?每次都要重新修改,未免有些麻烦了.所以我们可以使用方法来模块化这个累加的功能
1.2 方法的定义
语法
// 方法定义 public static 方法返回值 方法名称([参数类型 形参 ...]){ //方法体代码; [return 返回值];//视具体情况 } // 方法调用 返回值变量 = 方法名称(实参...);
以累加求和为例:
public class TestDemo { public static void main(String[] args) { int n = 100; int sum = Sum(n);//方法的调用 System.out.println(sum); } public static int Sum(int n){ //返回值类型 方法名 形参 int sum = 0; for (int i = 1; i <= n ; i++) { sum += i; } return sum;//返回值 } }
结果:5050
方法的使用方法和C大致相同,区别在于:
- 方法不需要另外声明,只要将方法写在类(
public class
)和main函数之间即可 public class
的特定含义后续会学习
1.3 方法调用的过程
规则(同C)
- 只有方法被调用,其代码才会被执行
- 方法被调用时,形参是实参的一份临时拷贝.参数传递后才会执行方法的具体代码,是纵向执行的.
- 遇到return语句(不论方法是否有返回值),方法都会结束执行,回到主函数的当前位置
1.4 实参与形参
例:交换两个整型变量的值
public static void main(String[] args) { int a = 1; int b = 2; Swap(a, b); System.out.println(a); System.out.println(b); } public static void Swap(int a, int b){ int tmp = a; a = b; b = tmp; }
结果:1 2
在学习C语言后,我们知道,这个函数并没有交换两个变量,得传地址才能交换.但Java中没有指针变量的概念,对于**基础类型,只能传值调用方法,形参是实参的一份临时拷贝
如何解决:传递引用类型(区别于基础类型)**参数,比如数组
public static void main(String[] args) { int[] arr = {1, 2}; 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; }
结果:2 1
2. 方法的重载(Overload)
2.1 方法重载存在的意义
例:加法方法
public static void main(String[] args) { int a = 1; int b = 2; int ret1 = Add(a, b); System.out.println(ret1); float c = 1.5f; float d = 2.5f; float ret2 = Add(c, d); System.out.println(ret2); } public static int Add(int a, int b){ return a + b; }
错误:java: 不兼容的类型: 从float转换到int可能会有损失
即使将ret2的值强转为float,编译器依然显示以上错误
原因是参数类型不匹配
方法重载的意义:
解决了同一个方法需要兼容多组不同的参数的问题
如何解决?
2.1 如何使用方法重载
public static void main(String[] args) { int a = 1; int b = 2; int ret1 = Add(a, b); System.out.println(ret1); float c = 1.5f; float d = 2.5f; float ret2 = Add(c, d); System.out.println(ret2); } public static int Add(int a, int b){ return a + b; } public static float Add(float a, float b){ return a + b; }
结果:
3
4.0
可以发现:调用的两个方法的名字是一样的,为什么编译器不会像C一样报错呢?
编译器是如何识别方法的?
- 每一个方法都有自己的签名:
菜鸟教程中对重载的描述:
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
注:参数类型列表即参数个数或类型
也就是说,方法的参数类型列表也是方法身份的一部分,不同于C,仅将函数名称作为函数的身份标识
2.3 方法重载的规则
在同一个类中
- 方法名相同
- 参数类型列表不同
- 返回值类型不影响重载,可以改变
代码实例见2.1
附:Java 8语法规则–8.4.9. Overloading
3. 方法的递归
3.1 递归的概念
方法的递归同C中函数的递归,即函数自身调用自身.
需要注意的是,递归并不能无限地调用自身.递归在数学中的体现是"数学归纳法"
递归的条件:
- 找到起始条件
- *找到递推公式
- *有一个不断趋近的(结束)条件,且存在一种简单情况,使得递归结束
例:求N!(假如N=3)
起始条件: N = 1!
结束条件 N = 3!
递推公式: N! = N * (N - 1)
如:3! = 3 * 2! = 3 * 2 * 1! = 3 * 2 * 1
例:用方法求N!
public static void main(String[] args) { int n = 5; int recursionNum = Recur(n); System.out.println(recursionNum); } public static int Recur(int n){ if(n == 1) return 1; return n * Recur(n - 1); }
结果:120
3.2 递归的执行过程
代码示例见3.1例
执行过程图解:以计算3!为例
需要注意的是:并不是变调用方法,边返回值(或执行该层对应的语句).
从这里可以体会到:我们口头上说的(即形象理解的)起始条件就是程序的终止条件,另一半反之.
例如:求3!
形象的理解:3! = 1 * 2 * 3
实际上程序是先把所有能够调用的地方全部调用完毕,才会返回值
3! = 3 * f(2)
= 3 * 2 * f(1)
= 3 * 2 * 1
小结
我们理解的起始和终止条件和程序真正地起始和终止条件是相反的
3.3 练习
例1:打印数字的每一位(1234)
起始条件:1234
终止条件:最后一位小于10(0~9)
递归公式:打印最后一位+去掉最后一位
public static void main(String[] args) { int n = 1234; printNum(n); } public static void printNum(int n){ if( n > 9){ printNum(n / 10); } System.out.print(n % 10 + " "); }
结果:1 2 3 4
此处体现了3.2提到的要点:方法调用到终止后,方法才会从最深的递归开始返回或者执行语句
例2:求1~10的和
起始条件:10
终止条件:1
递归公式:f(N) = N + f(N - 1)
public static void main(String[] args) { int n = 10; int num = addNum(n); System.out.println(num); } public static int addNum(int n){ if( n == 1 ){ return 1; } return n + addNum(n-1); }
结果:55
例3:写一个递归方法,输入一个非负整数,返回组成它的数字之和.
如1234,结果为1+2+3+4=10
起始条件:1234
终止条件:最后一位小于10(0~9)
递归公式:取出最后一位,边取边模10
public static void main(String[] args) { int n = 1234; int num = numAdd(n); System.out.println(num); } public static int numAdd(int n){ if(n < 10){ return n; } return n % 10 + numAdd(n / 10); }
结果:10
例4:求斐波那契数列第N项
假设N为5
起始条件:第1项为1,第2项为1
终止条件:同上
递归公式:从第三项开始,当前项等于前两项之和
public static void main(String[] args) { int n = 5; int num = fib(n); System.out.println(num); } public static int fib(int n){ if(n == 1 || n == 2){ return 1; } return fib(n - 1) + fib(n - 2); }
结果:5
补充:斐波那契数列用递归容易理解,但效率很低.
用循环实现求斐波那契数列第N项
思路:
利用递归公式:f(N) = N + f(N - 1),(N>2)
将三个数看成一个整体,每次循环走一步,三个数同时往后走一步
(这里很像三指针的迭代)
- 需要注意三个数往后迭代的顺序(为什么?)
求6!
public static void main(String[] args) { int num1 = 1; int num2 = 1; int num3 = 0; for(int i = 3; i <= 6; i++){//要从第三项开始 int cur = num3; num3 = num1 + num2; num1 = num2; num2 = num3; } System.out.println(num3); }
ps:面试的时候不要写递归的方法
总结:
- Java中的方法实际上就是C语言中的函数.最大的不同点就在于方法的重载等(目前仅学习了重载),Java将形参类型列表也当做方法身份的标识
- 递归的要素需要在写之前就想好,磨刀不误砍柴工.起始/终止条件,递归条件
- 重点理解递归的调用和返回(没有返回就执行语句)是两个不同的方向.我形象的认为递归的调用方向是向下的,复杂程度取决于调用层次的深度,返回或执行语句是自下而上的,不断积累.实际上我们理解的递归是自下而上的.