如何为此编写测试用例

时间:2013-10-19 18:31:24

标签: java recursion junit stack-overflow java-8

我被要求删除运行以下代码时发生的堆栈溢出异常 - 当提供大量数据时。这就是我被告知的全部内容。可悲的是,我不知道如何编写一个关于它的junit测试用例,因为我真的不明白这里发生了什么。有人可以帮助我理解这一点:

public interface FolderMaster<T, U>{        
U foldIt(U u, Queue<T> list, FunctionBi<T,U,U> bidi);    
}

public interface FunctionBi<T, U, R>{        
R applyIt(T t, U u);    
}

public class CommonFolder<T, U> implements FolderMaster<T, U>{
    public U foldIt(U u, Queue<T> ts, FunctionBi<T, U, U> bidi){
        if(u == null || ts == null || bidi == null)
            throw new IllegalArgumentException();

        if (ts.isEmpty()) {
            return u;
        }

        return foldIt(bidi.applyIt(ts.poll(), u), ts, bidi);
       }
}

由于FunctionBi与java.util.functoin.BiFunction紧密匹配,我查找java doc,但它只有一个接口方法apply。是否有任何课程证明了这门课程的用法?我想只是理解上面的代码是如何工作的。

2 个答案:

答案 0 :(得分:6)

正如我在评论中发表的那样,这基本上是功能编程中众所周知的功能“折叠”。

什么是折叠?它被称为折叠,因为它使用一些基本值和一些函数“折叠”给定的数据结构。在您的情况下,要折叠的数据结构是队列。基值是foldIt(u)的第一个参数,bidi函数是告诉你如何折叠它的函数。它使用3种类型进行推广,因为它包含2种类型并计算第3种类型的结果。

好的,什么?非常基本的折叠示例是:

u = 1;

队列=(2,3,4)

bidi(t,u)= return(t + u)

这里首先foldIt(u,queue,bidi)将添加1 + 2 = 3,然后调用foldIt(3,(3,4),bidi)。因此,您可以看到下一步的基值是上一步的bidi的结果。它一直持续到有折叠的元素并返回累积的(​​折叠的)值。

问题:现在问题是有人试图在JVM上以功能方式实现它,它不完全支持功能样式编程(即使在Java8中)。好的,JVM确实支持它(例如Scala这样做),但Java不支持它(原因不一定好)。

因为return语句是对foldIt()方法的调用,所以这不再称为尾递归。尾递归很好,因为你不需要为每个新调用都有一个新的堆栈帧,你可以重用当前的堆栈帧,因为在递归过程中没有必须保留的局部变量。

不幸的是,Java不支持尾调用优化,它会为每次调用foldIt()创建一个新的堆栈帧。

@Tassos Bassoukos已经发布了这个问题的解决方案,您只需要通过迭代将递归调用替换为foldIt()。通常怎么做?基本上,当您使用递归(或任何方法调用)时,计算机所做的是它为每个调用创建一个新的堆栈帧,其中包含有关它的信息(局部变量,一些指针等)并将其推送到当前的执行堆栈。因此,为了克服这个问题,您可以进行迭代并将所需的值推送到您自己的堆栈上,这可能会为您节省一些空间(您不需要存储计算机所需的指针,以了解它需要从递归中返回的内存中的哪个位置打电话等)。 在这种情况下(尾递归)它甚至更好,因为你没有局部变量,你可以跳过堆栈!像这样:

public class CommonFolder<T, U> implements FolderMaster<T, U>{
    public U foldIt(U u, Queue<T> ts, FunctionBi<T, U, U> bidi){
        if(u == null || ts == null || bidi == null)
            throw new IllegalArgumentException();

        while (!ts.isEmpty()) {
            u = bidi.applyIt(ts.poll(), u);
        }

        return u;
    }
}

这里你没有递归调用任何东西,所以你不会有太多新的堆栈帧(对于isEmpty()和applyIt()只有一个恒定的量)而没有堆栈溢出。

答案 1 :(得分:0)

手动尾部消除;你需要将递归转换为循环;涵盖任何CS学位。