Java:理解递归方法调用的困难

Java:理解递归方法调用的困难,java,recursion,methods,Java,Recursion,Methods,我制作了一个类似“俄勒冈州轨迹”的游戏,它使用“游戏结束”的方法询问用户是否想再次玩 主要问题是: 我的老师含糊其辞地提到,如果游戏循环次数足够多,我们最终会出现堆栈溢出。这对我来说很有意义,因为游戏继续按照我的方式将方法嵌套在彼此的内部,每次调用“新游戏”方法时都会添加到堆栈中,因为外部方法仍在等待完成 我已经总结了一个例子来说明我的意思。假设用户输入有暂停之类的情况,当我在其他方法中调用方法时,如何确保我的内存利用率不会不断增长?我认为这个词是“递归的”,因此我的标题是 如果有人能推荐正确的

我制作了一个类似“俄勒冈州轨迹”的游戏,它使用“游戏结束”的方法询问用户是否想再次玩

主要问题是: 我的老师含糊其辞地提到,如果游戏循环次数足够多,我们最终会出现堆栈溢出。这对我来说很有意义,因为游戏继续按照我的方式将方法嵌套在彼此的内部,每次调用“新游戏”方法时都会添加到堆栈中,因为外部方法仍在等待完成

我已经总结了一个例子来说明我的意思。假设用户输入有暂停之类的情况,当我在其他方法中调用方法时,如何确保我的内存利用率不会不断增长?我认为这个词是“递归的”,因此我的标题是

如果有人能推荐正确的处理方式,我将非常感激

public class Testing
{
    public static void main(String[] args) {
        System.out.println("main method");
        gameStart();
    }
    
    private static void gameStart()
    {
        System.out.println("some other method called");
        gameOver();
    }
    
    private static void gameOver()
    {
        System.out.println("game over called"); //I would ask the user if they want to play again.
        //keeping it concise to illustrate my point, instead of using an if statement
        gameStart();//starting the cycle I'm concerned about. Assume the user indicated they would like to play again.
    }
}

递归需要一个不会继续调用的条件。
递归最常见于方法调用自身的地方,例如计算斐波那契序列,其中

fib(n) == fib(n-1) + fib(n-2)
fib(0)
被定义为0,因此您不必计算。
fib(1)
被定义为1,因此您不必计算。
每隔一个数由调用自身两次的
fib()
方法计算,但它避免了对两个已定义的情况进行递归调用,在这两种情况下,没有任何计算。伪码

int fib(int n)
{
    if (n == 0) return 0; // doesnt have to recursively call
    if (n == 1) return 1; // same
    return fib(n-1) + fib(n-2);
}
在您的例子中,有两个方法相互调用,但您没有条件使调用可以从中逃脱

一种可能性是,当游戏以平局结束时,
gameOver()
只调用
gameStart()
,类似这样的

public class Testing
{
    public static void main(String[] args) {
        System.out.println("main method");
        gameStart();
    }

    private static void gameStart()
    {
        System.out.println("some other method called");
        gameOver();
    }

    private static void gameOver()
    {
        System.out.println("game over called");
        if (gameTied()) {
            gameStart();
        }
    }
}
如果你只是问“你想再玩一次吗?”——那最好在
main
中按照

public static void main(String[] args) {
    System.out.println("main method");
    String playGame = "Yes";
    while (playGame.equalsIgnoreCase("Yes") {
        gameStart();
        playGame = ask("Play again?");
    }
}

为了避免无限递归,您可以切换到迭代,并为那些当前决定如何继续的方法引入返回值(当前通过直接调用相应的操作)。 让这些方法返回一些符号,例如通过使用枚举来指示下一步要做什么。 然后编写一个循环,根据返回值调用正确的方法

示例(缩写,我假设您知道Java语法):

主要内容:

这假设每个这样的方法都会执行,直到决定下一步要采取的操作为止


这样,您的调用堆栈将始终非常平坦,因为执行总是返回到主方法,然后分支到其他方法。

当您编写递归代码时,除了再次调用函数外,还应确保您具有某种结束条件。例如,我用
if(gameplayed)为
gameOver
方法添加了一个结束条件。很多时候,你必须在赋值中使用递归,还是允许使用循环?因为在我看来,使用循环是最简单、最“自然”的方法处理这种情况的方法。我会用循环来解决这个特殊问题,而不必担心递归。现在,如果我们使用LISP…你可以用try-catch和
stackoverflowerrror
,然后处理catch块中的溢出。但是,捕捉错误是一种不好的做法,所以我不建议使用它。我必须使用递归继续。我制作的程序中有很多循环,但我们应该在不将整个游戏放入一段时间或循环的情况下完成这一步。顺便说一下,感谢所有帮助我的人。我只是想弄清楚如何在不重载堆栈的情况下继续调用相互调用的方法。比如,我可以清除堆栈,保留全局变量吗iables,当我以某种方式调用一个新方法时,对吗?这非常有意义。谢谢你的帮助。我真诚地感谢你。你在等待gameStart()的到来完成以测试再次播放的条件。非常感谢您的帮助。我仍然想知道是否可以启动一个方法并取消嵌套它的方法,但我认为这是不可能的。我并不完全理解这个答案。但是,我致力于理解您编写的内容。我知道java语法v到目前为止,我所学到的非常好,但我不明白你缩写的“枚举操作”行。这应该是另一个类吗?我还没有学会使用多个类,但我可以在明天到达。我已经看了5分钟,我不明白。我向你保证,我想了解你所学的一切完成。tbc…这对我来说很重要。但是,我还没有学会你正在缩写的概念。我只想说谢谢你花时间回答我。你太好了。好的,枚举在这里不是必需的。你可以用简单的数字来表示下一个动作(数字只是没有枚举那样具有描述性)-替换
Action.Start
Start
1
ShowEnd
2
Action.Quit
3
。如果用户决定再次玩游戏,您的方法
gameEnd()
应返回
1
(意思是开始),并应返回
3
(意思是退出)如果用户想要退出。main方法将在下一次循环迭代中进行评估并执行所需的操作。在现实世界中,这与此场景相对应:您(
main
)是老板,有几个专门的工人。您让他们中的一个执行任务(调用方法)您将得到一个报告,其中包含下一步要做什么的建议(
nextAction
)。然后您将查看该报告(
switch
),选择可以执行建议操作的工作人员(
case…
)并让其执行(方法调用)…此操作将重复(
)直到建议说你应该停下来。你描述这件事的方式很清楚。我还没有学会Enum,但今晚我会。这,连同其他一些答案,让我的生活更轻松。谢谢。这对我来说不仅仅是网站上的一些观点。你
enum Action { Start, ShowEnd, Quit }
Action nextAction = Action.Start;
while (action != Action.Quit)
{
    switch (action)
    {
        case Start:
            nextAction = gameStart();
            break;
        case ShowEnd:
            nextAction = gameEnd();
            break;
        // ToDo: write more actions!
        default:
            break;
    }
}
public static void main(String[] args)
{
    System.out.println("main method");
    gameStart(10);
}

private static void gameStart(int playGameThisManyTimes)
{
    System.out.println("Game " + playGameThisManyTimes + " started...");
    System.out.println("some other method called");
    gameOver(playGameThisManyTimes);
}

private static void gameOver(int gamePlayedThisManyTimes)
{
    System.out.println("game over called for " + gamePlayedThisManyTimes); //I would ask the user if they want to play again.

    if(gamePlayedThisManyTimes <= 1)
        return;
    else
        gameStart(gamePlayedThisManyTimes - 1);
}
main method
Game 10 started...
some other method called
game over called for 10
Game 9 started...
some other method called
game over called for 9
Game 8 started...
some other method called
game over called for 8
Game 7 started...
some other method called
game over called for 7
Game 6 started...
some other method called
game over called for 6
Game 5 started...
some other method called
game over called for 5
Game 4 started...
some other method called
game over called for 4
Game 3 started...
some other method called
game over called for 3
Game 2 started...
some other method called
game over called for 2
Game 1 started...
some other method called
game over called for 1