先说概念:

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指令是用来执行函数
在这里插入图片描述

  1. 我们看“普通递归”,在红线那一行 13: invokevirtual #20 // Method fun2:(I)I,对应着fun2(x-1)+1这条命令。
    这代表 这里的调用自己,是真的在调用自己。使用invokevirtual再次进入自己

在这里插入图片描述

  1. 这次呢,是尾递归,我们发现这次没有invokevirtual 了呢,为之代替的是一个名为goto的指令。他goto到哪了呢,到0这行了。也就是这个函数的开头。这也就是循环的代表。
    在这里插入图片描述

结论:

看来确实如此呢

Scala的尾递归会被编译器自动优化成循环