一、方法的基本用法
方法就是指将一段代码封装在一个结构体之中,实现某种功能,可以被重复调用的代码块。
方法的意义
- 模块化的组织代码,使程序变得更简短而清晰。
- 有利于程序维护。
- 可以提高程序开发的效率。
- 提高了代码的重用性。
基本语法
// 方法定义
public static 方法返回值 方法名称([参数类型 形参 ...]){
方法体代码;
[return 返回值];
}
// 方法调用
返回值变量 = 方法名称 (实参...);
方法调用的执行过程
1.定义方法的时候, 不会执行方法的代码. 只有调用的时候才会执行;
2.当方法被调用的时候, 会将实参赋值给形参;
3.参数传递完毕后, 就会执行到方法体代码;
4.当方法执行完毕之后(遇到 return 语句), 就执行完毕, 回到方法调用位置继续往下执行;
5.一个方法可以被多次调用。
方法中实参和形参的关系
形参:就是形式参数,用于定义方法的时候使用的参数,是用来接收调用者传递的参数的。形参只有在方法被调用的时候,虚拟机才会分配内存单元,在方法调用结束之后便会释放所分配的内存单元。
实参:就是实际参数,用于调用时传递给方法的参数。实参在传递给别的方法之前是要被预先赋值的。
代码示例1: 交换两个整型变量
class Test {
public static void main(String[] args) {
int a = 10;
int b = 20;
swap(a, b);
System.out.println("a = " + a + " b = " + b);
}
public static void swap(int x, int y) {
int tmp = x;
x = y;
y = tmp;
}
}
// 运行结果
a = 10 b = 20
上面的swap方法是交换两个整数的值,但是在主函数中调用后a和b的值并没有发生改变,为什么呢?
因为对于基础类型来说, 形参相当于实参的拷贝,即 传值调用。
代码示例2:交换数组中两个整数的值
class Test {
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
在这段代码中可以发现arr[0]和arr[1]的值发生了交换,为什么这段代码可以发生交换呢?
因为在Java中数组元素的变量名保存的数组中元素的地址,交换实际上是改变了指针的指向,所以看起来是完成了数值的交换。
结论:
- 当方法中参数是基本数据类型时实参和形参之间是按值传递。
- 方法中参数是引用类型 时实参和形参按引用传递。
二、方法的重载
Java中方法的重载,是指若同一个类中,两个或两个以上方法名相同,但是调用的方法参数的个数、顺序或类型不同的方法,则称为方法的重载。
重载的规则
在同一个类中:
- 方法名相同。
- 方法的参数不同(参数个数或者参数类型,顺序不同)。
- 方法的返回值类型不影响重载,相同或者不同都可以。
重载示例1
class Test {
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);
}
//第一个add方法,两个int类型相加
public static int add(int x, int y) {
return x + y;
}
//第二个add方法,两个double类型相加
public static double add(double x, double y) {
return x + y;
}
//第三个add方法,三个add类型相加
public static double add(double x, double y, double z) {
return x + y + z;
}
}
方法的名字都叫 add. 但是有的 add 是计算 int 相加, 有的是 double 相加; 有的计算两个数字相加, 有的是计算三个数字相加。调用时会根据传的参数的不同,自动调用合适的方法。
同一个方法名字, 提供不同版本的实现, 称为方法重载。
重载示例2
class Test {
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;
}
}
// 编译出错
Test.java:13: 错误: 已在类 Test中定义了方法 add(int,int)
public static double add(int x, int y) {
^
1 个错误
当两个方法的名字相同, 参数也相同, 但是返回值不同的时候, 不构成重载。
关于方法重载和重写的区别:
1.方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性;
2.重载发生在一个类中,重写发生在子类与父类之间;
3.同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者顺序不同)则视为重载;重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。
4.重载对返回类型没有特殊的要求,不能根据返回类型进行区分。
三、方法的递归
如果一个方法在执行过程中调用了自己本身,就称作“递归”。
使用递归时的注意事项:
1.一定要有一个终止的条件,保证递归能够停止下来,否则就会发生内存溢出。
2. 在递归中虽然有限定条件,但是递归次数不能太多,否则也会发生栈内存溢出;
3. 构造方法禁止递归,因为构造方法是创建对象使用的,不能让对象一直创建下去把。
经典递归代码:求斐波那契数列的第 N 项
斐波那契数列为1、1、2、3、5、8、13、21、34……此数列从第3项开始,每一项都等于前两项之和,递推公式为F(n)=F(n-1)+F(n-2),n≥3,F(1)=1,F(2)=1。
代码:
public static int fib(int n) {
if (n == 1 || n == 2) {
return 1;
}
return fib(n - 1) + fib(n - 2);
}
代码很简洁,但是有一个问题,随着N变大,程序执行速度极慢,原因是进行了大量的重复运算。
比如可以用一段代码来测一下:
class Test {
public static int count = 0;
public static void main(String[] args) {
System.out.println(fib(40));
System.out.println(count);
}
public static int fib(int n) {
if (n == 1 || n == 2) {
return 1;
}
if (n == 3) {
count++;
}
return fib(n - 1) + fib(n - 2);
}
}
// 执行结果
102334155
39088169 // fib(3) 重复执行了 3 千万 次
通过上面的代码可以看到计算到第40个时,仅仅是fib(3)就被重复执行了三千万次。可想而知运行速度有多慢。
对于上面的代码,可以改为使用循环的方式来求, 避免出现冗余运算。
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;
}
此时程序的执行效率大大提高了 。
递归小结:
- 优点:代码简洁、清晰,并且容易验证正确性。
- 缺点:它的运行需要较多次数的函数调用,如果调用层数比较深,需要增加额外的堆栈处理,比如参数传递需要压栈等操作,会对执行效率有很大影响。但是,对于某些问题天然就是使用递归方式定义的(例如斐波那契数列, 二叉树等), 此时使用递归来解就很容易,使用非递归代码会很难看。有些问题使用递归和使用非递归(循环)都可以解决时. 那么此时更推荐使用循环, 因为相比于递归, 非递归程序更加高效。