先说概念:
Scala的尾递归会被编译器自动优化成循环
先来简单看下一个简单验证方法
对比普通的递归:
def fun2(x: Int): Int = {
if (x == 1)
throw new Exception("nooo")
else
fun2(x - 1) + 0
}
结果:
Exception in thread "main" java.lang.Exception: nooo
at Main$.fun2(Main.scala:17)
at Main$.fun2(Main.scala:19)
at Main$.fun2(Main.scala:19)
at Main$.fun2(Main.scala:19)
at Main$.fun2(Main.scala:19)
。。。。
at Main$.fun2(Main.scala:19)
at Main$.main(Main.scala:25)
at Main.main(Main.scala)
现象:
我们能看到在递归结束前,这个方法已经进入自己很多次了。
尾递归
def fun(x: Int): Int = {
if (x == 0)
throw new Exception("nooo")
else
fun(x - 1)
}
结果:
Exception in thread "main" java.lang.Exception: nooo
at Main$.fun(Main.scala:9)
at Main$.main(Main.scala:25)
at Main.main(Main.scala)
现象
只进入这个方法一次
结论
从中我们能看到尾递归的确不会一直调用自己
。
字节码验证
假设咱们知道了JITWatch的使用。
1.先看主函数,从bytecode区域,我们能看到在标号为8 这一行
8: invokevirtual #30 // Method fun:(I)I
得知 invokevirtual
指令是用来执行函数
- 我们看“普通递归”,在红线那一行
13: invokevirtual #20 // Method fun2:(I)I
,对应着fun2(x-1)+1
这条命令。
这代表 这里的调用自己,是真的在调用自己。使用invokevirtual
再次进入自己
- 这次呢,是尾递归,我们发现这次没有
invokevirtual
了呢,为之代替的是一个名为goto
的指令。他goto到哪了呢,到0这行了。也就是这个函数的开头。这也就是循环的代表。
结论:
看来确实如此呢
Scala的尾递归会被编译器自动优化成循环