为什么这个Groovy闭包没有返回我期望的值?

时间:2012-02-22 00:47:53

标签: grails groovy

在Grails应用程序中,我试图阻止在有向图中创建循环。用户可以将父节点分配给节点,但任何节点都不应该是其父节点的祖先。我编写了一个简单的设置函数,它调用checkLineageForTarget,它是执行繁重任务的递归函数:

boolean checkLineageForTarget(Integer target, Collection<Node>stillToProcess){
// true means that this is a safe addition
// false means that this addition creates a cycle

    boolean retVal = stillToProcess.each {
        Collection<Node> itsParents = getParentNodes(it)

        if (it.id == target){
            println("found a loop on " + target);
            return false; // loop detected!
        }
        if (itsParents.empty){ return true; } // end of the line

        return checkLineageForTarget(target, itsParents)
    }

    // at this point, retVal is always true, even when the "found a loop [...]" condition is met
    return retVal;
}

这“工作”,因为它打印“找到一个循环[...]”消息,但在闭包之外,retVal为true,调用函数尝试添加新的父/子关系,而我的堆栈结束。

我的误解是什么?

3 个答案:

答案 0 :(得分:3)

.each似乎在完成后返回Object循环。您将此分配给布尔值,并且它被强制转换为true。您可能希望使用.every来完成任务。仅当每次迭代返回true时它才返回true,并且当它到达第一个false时它将停止循环。您可以找到更多信息in the groovy docs

答案 1 :(得分:3)

each方法返回调用它的同一个集合,因此retVal可能不是布尔值“true”,但被评估为“真实”(因为它是一个集合,它会意味着它不是空的。)

如果要检查集合中每个元素的条件,可以使用every

boolean checkLineageForTarget(Integer target, Collection<Node>stillToProcess){
    stillToProcess.every { node ->
        node.id != target && checkLineageForTarget(target, getParentNodes(node))
    }
}

请注意,我不需要检查父节点集合上的.empty条件,因为它将通过对checkLineageForTarget的递归调用进行过滤(即,对空集合调用.every始终返回true) 。此外,由于&&运算符的短路,迭代会在node.id == target后立即停止:)

答案 2 :(得分:2)

当你在Closure内部返回时,它就像返回方法中的方法调用一样 - 它是该范围的本地调用,并且对调用闭包的实际方法没有影响。在这种情况下,你可以使用其中一个建议的其他方法(例如每个)或使用常规for循环,因为它的工作方式与每个Groovy相同(即它是null安全且支持但不需要类型)但是你可以突破循环或返回,因为你'在一个真正的for循环中,它将从方法返回:

boolean checkLineageForTarget(Integer target, Collection<Node>stillToProcess){

   for (Node node in stillToProcess) {
      Collection<Node> itsParents = getParentNodes(node)
      ...
   }
   ...
}