在 Groovy 闭包中模拟“继续”的最佳模式

Posted

技术标签:

【中文标题】在 Groovy 闭包中模拟“继续”的最佳模式【英文标题】:Best pattern for simulating "continue" in Groovy closure 【发布时间】:2010-09-17 08:47:54 【问题描述】:

Groovy 似乎不支持闭包内的breakcontinue。模拟这种情况的最佳方法是什么?

revs.eachLine  line -> 
    if (line ==~ /-28/) 
         // continue to next line...
    

【问题讨论】:

【参考方案1】:

你只能支持干净地继续,不能中断。尤其是像 eachLine 和 each 这样的东西。无法支持中断与如何评估这些方法有关,没有考虑没有完成可以传达给方法的循环。以下是如何支持继续 --

最佳方法(假设您不需要结果值)。

revs.eachLine  line -> 
    if (line ==~ /-28/) 
        return // returns from the closure
    

如果你的示例真的那么简单,这对可读性很有好处。

revs.eachLine  line -> 
    if (!(line ==~ /-28/)) 
        // do what you would normally do
    

另一个选项,模拟 continue 在字节码级别通常会做什么。

revs.eachLine  line -> 
    while (true) 
        if (line ==~ /-28/) 
            break
        
        // rest of normal code
        break
    


支持中断的一种可能方法是通过异常:

try 
    revs.eachLine  line -> 
        if (line ==~ /-28/) 
            throw new Exception("Break")
        
    
 catch (Exception e)   // just drop the exception

您可能希望使用自定义异常类型来避免屏蔽其他真正的异常,尤其是当您在该类中进行其他可能引发真正异常的处理时,例如 NumberFormatExceptions 或 IOExceptions。

【讨论】:

使用异常来控制程序流程是个坏主意。创建异常需要获取调用堆栈的快照,而且成本很高。 如果您在抛出什么都不做的异常中覆盖生成调用堆栈的方法,则不会。这就是自定义异常的优势。 这也是解决问题的额外步骤,如果您使用闭包之类的闭包而不是将其视为循环构造,则不会遇到问题。上面的示例将更多地受益于修复逻辑的不清楚的整体意图以过滤行或查找行。【参考方案2】:

闭包不能中断或继续,因为它们不是循环/迭代结构。相反,它们是用于处理/解释/处理迭代逻辑的工具。您可以通过简单地从闭包返回而不进行处理来忽略给定的迭代:

revs.eachLine  line -> 
    if (line ==~ /-28/) 
            return
    


中断支持不会发生在闭包级别,而是由接受闭包的方法调用的语义所暗示。简而言之,这意味着不要在诸如旨在处理整个集合的集合之类的东西上调用“每个”,而应该调用 find 它将一直处理直到满足特定条件。大多数(全部?)时间你觉得需要打破闭包,你真正想做的是在迭代期间找到一个特定的条件,这使得 find 方法不仅符合你的逻辑需求,而且符合你的意图。遗憾的是,一些 API 缺乏对 find 方法的支持……例如 File。有可能花在争论语言是否应该包括中断/继续的所有时间都花在了将 find 方法添加到这些被忽视的区域上。像 firstDirMatching(Closure c) 或 findLineMatching(Closure c) 这样的东西会走很长一段路,并回答 99+% 的“为什么我不能摆脱......?”邮件列表中弹出的问题。也就是说,通过 MetaClass 或 Categories 自己添加这些方法是微不足道的。

class FileSupport 
   public static String findLineMatching(File f, Closure c) 
      f.withInputStream 
         def r = new BufferedReader(new InputStreamReader(it))
         for(def l = r.readLine(); null!=l; l = r.readLine())
             if(c.call(l)) return l
         return null
      
   


using(FileSupport)  new File("/home/me/some.txt").findLineMatching  line ==~ /-28/ 

其他涉及异常和其他魔法的 hack 可能会起作用,但在某些情况下会引入额外的开销,并在其他情况下会影响可读性。真正的答案是查看您的代码并询问您是否真正在迭代或搜索。

【讨论】:

【参考方案3】:

如果您在 Java 中预先创建一个静态 Exception 对象,然后从闭包内抛出(静态)异常,则运行时成本是最低的。真正的成本是在创建异常时产生的,而不是在抛出它时。根据 Martin Odersky(Scala 的发明者)的说法,许多 JVM 实际上可以将 throw 指令优化为单次跳转。

这可以用来模拟休息:

final static BREAK = new Exception();
//...
try 
  ...  throw BREAK; 
 catch (Exception ex)  /* ignored */ 

【讨论】:

那个……真是个好主意。将此与枚举结合起来,为每个枚举构建一个预先创建的异常。哎呀-在抛出它的枚举中放置一个方法。 Condition.BAD_WEATHER.fire(); 其实这不是我的主意。 Martin Odersky(Scala 的发明者)在gave 的一次演讲中将其作为 Scala 中同一问题的解决方案。多年前,我在 Rensselaer 的数据结构课程中有一位教授展示了一个不使用 goto 语句的问题的解决方案,然后使用 gotos 提供了相同的解决方案。后者短得多。他的观点是,有时实用性胜过优雅。【参考方案4】:

使用 return继续any 关闭来中断

示例

文件内容:

1
2
----------------------------
3
4
5

Groovy 代码:

new FileReader('myfile.txt').any  line ->
    if (line =~ /-+/)
        return // continue

    println line

    if (line == "3")
        true // break

输出:

1
2
3

【讨论】:

你真是个天才。小心.any。如果闭包中的最后一条语句与 'true' 相关联,它将停止。如果您希望您的逻辑或您希望您的文件被完全处理,请将“false”作为任何闭包内的最后一条语句。我花了几天时间才看到这篇文章。 如果你只用line == "3" 替换最后一个 if 块,我觉得会更清楚一些【参考方案5】:

在这种情况下,您可能应该想到find() 方法。它在第一次传递给它的闭包返回 true 后停止。

【讨论】:

【参考方案6】:

使用rx-java,您可以将可迭代对象转换为可观察对象。

然后您可以将 continue 替换为 filter 并将 break 替换为 takeWhile

这是一个例子:

import rx.Observable

Observable.from(1..100000000000000000)
          .filter  it % 2 != 1 
          .takeWhile  it<10  
          .forEach println it

【讨论】:

以上是关于在 Groovy 闭包中模拟“继续”的最佳模式的主要内容,如果未能解决你的问题,请参考以下文章

Groovy闭包 Closure ( 闭包类 Closure 简介 | thisownerdelegate 成员区别 | 静态闭包变量 | 闭包中定义闭包 )

Groovy闭包 Closure ( 闭包定义 | 闭包类型 | 查看编译后的字节码文件中的闭包类型变量 )

Groovy闭包 Closure ( 闭包类 Closure 简介 | 闭包 parameterTypes 和 maximumNumberOfParameters 成员用法 )

Groovy闭包 Closure ( 闭包类 Closure 简介 | 闭包 parameterTypes 和 maximumNumberOfParameters 成员用法 )

Groovy闭包 Closure ( 闭包作为函数参数 | 代码示例 )

Groovy闭包 Closure ( 闭包作为函数参数 | 代码示例 )