在数学中会介绍一个寻找多个变量之间的关系的方法——递归。那里的递归和java中的递归虽然用处不一样,但体现的原理却是一样的。递归的原理总结下来可以用俄罗斯套娃来概括,就是不停地嵌套同一个方法内容。因此在没有添加执行条件的情况下,递归的运行是一个死循环。
在java中,方法与方法之间在合理的情况下可以相互调用,比如在a方法中调用b方法,b方法中也可以调用a方法。那么一个方调用自己会是一个什么情况呢,这里让我们把思绪回到之前对内存结构的分析之上。在《对象创建过程的内存图解》一文中,我们通过内存模型简要介绍了程序运行时内存的变化过程,因此我们知道,当调用一个方法时,程序会在栈空间中开辟一个栈帧,当方法运行结束的时候程序才会从栈空间中退出,栈帧关闭。
这里我们用a方法来表示一个将要进行递归的方法。当我们在a法中调用a方法自己时,首先会在栈内存中第一次开辟a方法的栈帧,随后在a方法中调用了a方法,那么就会在栈内存中第二次开辟属于a方法的栈帧,而第一次调用的a方法中又调用了a方法,因此会在栈内存中第三次开辟a方法的栈帧……以此类推。并且能够发现,犹豫在我们不停地在a方法中调用a方法,所以第一次开辟的栈帧并没有关闭,也就是说在程序结束前,栈内存中会不停地开辟属于a方法的栈帧,而这些开辟的栈帧并没又被关闭,这样下去,总有一个时刻栈内存会被用完,也就是栈内存会溢出,这时由于栈内存完全被占用,程序就会报错停止。也就是说如果不加约束,那么递归就时一个运行到一定时间会停下来的死循环。 所以在使用递归时,由于频繁的开辟栈内存会造成资源的被大量占用的情况。
为了避免上述的不停开辟栈帧的情况,在使用递归时就必须要添加条件,只有在满足这些条件的时候才会在a方法中调用a方法,这样就能让递归合理地停下来,避免了栈空间溢出的异常情况。比如下面的演示代码所示:
/**
* 测试递归
*/
public class Test01 {
public static int num = 12;
public static void a(){
num--;
System.out.println("Test01.a");
if(num>0){
a();}
}
public static void b(){
num--;
System.out.println("Test01.b");
}
public static void main(String[] args) {
a();
}
}
接下来我们用一个程序来实现求一个数的阶乘,并对其进行分析,用以加强对递归的理解。
在下面的这个程序中用了递归的方法和循环的方法来实现求一个数的阶乘这个功能。这里只对递归的运行进行分析,以数字10为例,在factorial方法中,如果n = 0,则会返回值1,这是阶乘的规则。接下来分析当输入数字10的时候,由于不满足你n = 0 的情况程序会执行递归结构n*factorial(n-1),此时的执行结果为10*factorial(10-1),由于9也不等于0,于是执行结果变为10*9*factorial(9-1),再一次因为8不等于0,因此执行结果变成10*9*8*factorial(8-1)……就这样一直执行下去直到运行结果为10*9*8*7*6**5*4*3*2*1*factorial(1-1)时,由于1-1=0,所以factorial(0)返回的值是1,并且此时不再调用factorial方法,递归结束,返回10的阶乘的值。
在上面的这个分析过程中为了便于理解我们并没有将调用factorial方法之前的结果进行计算,在实际的运行结果中运行结果应该为10*factorial(9)、90*factorial(8)、720*factorial(7)……所以当执行到factorial(0)时10的阶乘的结果就已经出来了。
此外还应注意到一点,虽然递归提供了良好的封装性,使得在main方法中的代码大大简化,但是通过运行时间的对比不难发现,递归所用的时间比循环多得多,这是因为在递归中频繁调用方法导致的,这算是递归的一个缺点。
package cn.digui.demo;
import java.util.Scanner;
public class Test02 {
public static void main(String[] args) {
long time1 = System.currentTimeMillis();
Scanner sc = new Scanner(System.in);
System.out.println("请输入需要计算的数据");
int n = sc.nextInt();
System.out.printf("递归的计算结果为:"+"%d!的结果是:%s%n",n,factorial(n));
long time2 = System.currentTimeMillis();
System.out.printf("递归程序程序总耗时:%s",time2-time1);
System.out.println();
long time3 = System.currentTimeMillis();
long result = 1;
int i = sc.nextInt();
while(i>=1){
result *= i*(i-1);
i -= 2;
}
System.out.println("循环结构的运行结果为:"+n+"!="+result);
long time4 = System.currentTimeMillis();
System.out.println("循环所用时间:"+(time4-time3));
}
public static long factorial(int n){
if(n == 0){
return 1;
}else {
return n*factorial(n-1);
}
}
}