1.什么是遞歸?
程序調用自身的編程技巧稱為遞歸( recursion),就好像你做了一個夢,夢里面夢到夢到自己又做了一個夢,夢中夢。下面看一個例子:
public class JavaTest {
public static int method(int num) {
if (num > 1)
return num + method(num - 1);
else
return num;
}
public static void main(String[] args){
int sum =method(100);//從一加到一百的和
System.out.println(sum);
}
}
這樣遞歸的話呢,看上去代碼很簡潔,但是遞歸是存在缺點的,就是在遞歸調用的過程當中系統為每一層的返回點、局部量等開辟了棧來存儲,因此遞歸次數過多容易造成棧溢出。舉個栗子,就是打開一扇門,然后又進去了一扇門,等到return的時候,會從里向外一個一個的return,一個一個的出來,所以之前的這些“門”是占要地方的,遞歸層級過深就會造成堆棧溢出。那么怎么解決這個問題呢?答案就是尾遞歸優化。
2.什么是尾遞歸?
如果一個函數中所有遞歸形式的調用都出現在函數的末尾,我們稱這個遞歸函數是尾遞歸的。當遞歸調用是整個函數體中最后執行的語句且它的返回值不屬于表達式的一部分時,這個遞歸調用就是尾遞歸。
public class JavaTest {
public static int method(int num,int sum) {
if (num > 1) {
sum += num;
return method(num - 1,sum);
}
else {
return sum+1;
}
}
public static int method(int num) {
return method(num,0);
}
public static void main(String[] args){
int sum =method(100);
System.out.println(sum);
}
}
如上,使用了一個變量sum保存了遞歸調用的結果,并傳到下一次遞歸調用中,這就是尾遞歸。到遞歸的結尾的時候,就可以直接return,不需要一層一層的出來。這樣的話,就不用保存前面那些函數的堆棧,也就不會堆棧溢出了。
3.編譯器的優化
雖然手寫成了尾遞歸的形式,但是編譯成字節碼的時候,優不優化得看編譯器的心情,所以很蛋疼。編譯器如果支持尾遞歸優化,那么就會利用尾遞歸特點來進行優化,在遞歸調用的時候重復使用同一個函數棧幀,效率很高,however,java還沒有實現尾遞歸優化的支持,官方是推薦使用循環,迭代,不使用遞歸。