我喜欢递归,但在Java中,你在某个时刻遇到了死胡同。例如。我有一个案例,其中~100K迭代的递归不起作用(StackOverflowError)。糟糕的是,我不得不为这个运行时堆栈限制原因切换到恼人的“命令性循环”。
我想知道其他(特别是功能性)语言在运行时是如何绕过堆栈溢出的?我想特别是函数式语言运行时更好地处理这个问题,因为递归是核心概念......
有人提供某些信息或外部资源吗?
答案 0 :(得分:8)
大多数语言都对tail recursion进行了编译器优化。尾递归意味着递归调用应该是递归方法的最后一次调用。然后,编译器可以将其优化为循环,防止发生堆栈溢出错误。
我不确定所有javac
实现是否都实现了尾递归。它不是规范要求的东西。但是,它是任何递归程序的重要优化技术,所以我认为主流实现确实提供尾递归。
您可以通过采用生成StackOverflowError
的(简单)非尾递归程序并使其尾递归(例如计算factorial)来自行测试。
编辑:之前在Java中有question about tail recursion,如用户sje397的评论中所示。另请查看此问题的Stephen C's answer,其中提供了一些其他信息。
答案 1 :(得分:3)
这是@Ronald Wildenberg回答的后续内容:
我不确定所有javac实现是否都实现了尾递归。它不是规范要求的东西。
简短的回答是他们不支持它。
更长的答案是,由于JVM设计,尾递归优化在Java中是一个棘手的问题。阅读John Rose @ Oracle的this blog entry,他谈到了这个问题。博客条目的主旨是提出字节码扩展以支持“硬”尾调用。但最后一段暗示为什么实施“软”(即透明)尾部调用很难。尾调用优化会干扰JVM捕获堆栈跟踪的能力,这对Java安全体系结构具有“影响”。
此Sun Bug Database Entry提供了有关此问题的更多详细信息。阅读评论。
似乎在当前JVM上实现尾递归的方法是在编译器前端实现它。显然Scala就是这样做的。
答案 2 :(得分:2)
“带有~100K迭代的递归”是应该避免的,而不仅仅是Java。它仅适用于尾部调用优化,但这并不总是可行的。因此,最好不要首先养成过度递归的习惯。
递归是人们在第一次学习它们时往往会过度使用的概念之一,因为它似乎太酷了,不能到处展示......
答案 3 :(得分:0)
您可以使用以下命令增加java堆栈大小:
java -Xss1024k MyProgram
(将java的堆栈大小增加到1024 KB。) 但通常使用深度递归并不是一个好主意。尝试制作迭代解决方案。
答案 4 :(得分:0)
正如其他人所提到的,支持正确的尾递归有帮助。但是它本身并不足以支持深度递归。尽管BLUB程序员可能会想到,深度递归很适合某些任务,例如处理深度递归的数据结构。
支持深度(非尾部)递归的策略通常归入支持一流延续的策略中。您可能会发现Implementation Strategies for First-Class Continuations(Clinger等人)很有趣。
我的头脑中有一些策略: